From 5780a1797115554ebff370bd4634420ddba4fc0f Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 18 Dec 2024 18:07:16 +0100 Subject: [PATCH 01/54] Consolidate 23.976 trim computation. --- client/ayon_core/pipeline/editorial.py | 81 +++++--- .../plugins/publish/extract_otio_review.py | 6 +- .../resources/qt_23.976_embedded_long_tc.json | 174 ++++++++++++++++++ .../editorial/test_extract_otio_review.py | 22 +-- .../test_media_range_with_retimes.py | 22 +++ 5 files changed, 262 insertions(+), 43 deletions(-) create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/qt_23.976_embedded_long_tc.json diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index 2928ef5f63..d71cf6c344 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -196,11 +196,11 @@ def is_clip_from_media_sequence(otio_clip): return is_input_sequence or is_input_sequence_legacy -def remap_range_on_file_sequence(otio_clip, in_out_range): +def remap_range_on_file_sequence(otio_clip, otio_range): """ Args: otio_clip (otio.schema.Clip): The OTIO clip to check. - in_out_range (tuple[float, float]): The in-out range to remap. + otio_range (otio.schema.TimeRange): The trim range to apply. Returns: tuple(int, int): The remapped range as discrete frame number. @@ -211,17 +211,25 @@ def remap_range_on_file_sequence(otio_clip, in_out_range): if not is_clip_from_media_sequence(otio_clip): raise ValueError(f"Cannot map on non-file sequence clip {otio_clip}.") - try: - media_in_trimmed, media_out_trimmed = in_out_range - - except ValueError as error: - raise ValueError("Invalid in_out_range provided.") from error - media_ref = otio_clip.media_reference available_range = otio_clip.available_range() - source_range = otio_clip.source_range available_range_rate = available_range.start_time.rate - media_in = available_range.start_time.value + + # Backward-compatibility for Hiero OTIO exporter. + # NTSC compatibility might introduce floating rates, when these are + # not exactly the same (23.976 vs 23.976024627685547) + # this will cause precision issue in computation. + # Currently round to 2 decimals for comparison, + # but this should always rescale after that. + rounded_av_rate = round(available_range_rate, 2) + rounded_range_rate = round(otio_range.start_time.rate, 2) + + if rounded_av_rate != rounded_range_rate: + raise ValueError("Inconsistent range between clip and provided clip") + + source_range = otio_clip.source_range + source_range_rate = source_range.start_time.rate + media_in = available_range.start_time available_range_start_frame = ( available_range.start_time.to_frames() ) @@ -236,14 +244,20 @@ def remap_range_on_file_sequence(otio_clip, in_out_range): and available_range_start_frame == media_ref.start_frame and source_range.start_time.to_frames() < media_ref.start_frame ): - media_in = 0 + media_in = otio.opentime.RationalTime( + 0, rate=available_range_rate + ) + src_offset_in = otio_range.start_time - media_in frame_in = otio.opentime.RationalTime.from_frames( - media_in_trimmed - media_in + media_ref.start_frame, + media_ref.start_frame + src_offset_in.to_frames(), rate=available_range_rate, ).to_frames() + + range_duration = otio_range.duration + frame_out = otio.opentime.RationalTime.from_frames( - media_out_trimmed - media_in + media_ref.start_frame, + frame_in + otio_range.duration.to_frames() - 1, rate=available_range_rate, ).to_frames() @@ -374,31 +388,44 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): offset_in, offset_out = offset_out, offset_in handle_start, handle_end = handle_end, handle_start - # compute retimed range - media_in_trimmed = conformed_source_range.start_time.value + offset_in - media_out_trimmed = media_in_trimmed + ( - ( - conformed_source_range.duration.value - * abs(time_scalar) - + offset_out - ) - 1 - ) - - media_in = available_range.start_time.value - media_out = available_range.end_time_inclusive().value - # If media source is an image sequence, returned # mediaIn/mediaOut have to correspond # to frame numbers from source sequence. if is_input_sequence: + + src_in = conformed_source_range.start_time + src_duration = conformed_source_range.duration + + offset_in = otio.opentime.RationalTime(offset_in, rate=src_in.rate) + offset_duration = otio.opentime.RationalTime(offset_out, rate=src_duration.rate) + + trim_range = otio.opentime.TimeRange( + start_time=src_in + offset_in, + duration=src_duration + offset_duration + ) + # preserve discrete frame numbers media_in_trimmed, media_out_trimmed = remap_range_on_file_sequence( otio_clip, - (media_in_trimmed, media_out_trimmed) + trim_range, ) media_in = media_ref.start_frame media_out = media_in + available_range.duration.to_frames() - 1 + else: + # compute retimed range + media_in_trimmed = conformed_source_range.start_time.value + offset_in + media_out_trimmed = media_in_trimmed + ( + ( + conformed_source_range.duration.value + * abs(time_scalar) + + offset_out + ) - 1 + ) + + media_in = available_range.start_time.value + media_out = available_range.end_time_inclusive().value + # adjust available handles if needed if (media_in_trimmed - media_in) < handle_start: handle_start = max(0, media_in_trimmed - media_in) diff --git a/client/ayon_core/plugins/publish/extract_otio_review.py b/client/ayon_core/plugins/publish/extract_otio_review.py index 712ae7a886..d5f5f43cc9 100644 --- a/client/ayon_core/plugins/publish/extract_otio_review.py +++ b/client/ayon_core/plugins/publish/extract_otio_review.py @@ -209,13 +209,9 @@ class ExtractOTIOReview( # File sequence way if is_sequence: # Remap processing range to input file sequence. - processing_range_as_frames = ( - processing_range.start_time.to_frames(), - processing_range.end_time_inclusive().to_frames() - ) first, last = remap_range_on_file_sequence( r_otio_cl, - processing_range_as_frames, + processing_range, ) input_fps = processing_range.start_time.rate diff --git a/tests/client/ayon_core/pipeline/editorial/resources/qt_23.976_embedded_long_tc.json b/tests/client/ayon_core/pipeline/editorial/resources/qt_23.976_embedded_long_tc.json new file mode 100644 index 0000000000..01d81508d1 --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/qt_23.976_embedded_long_tc.json @@ -0,0 +1,174 @@ +{ + "OTIO_SCHEMA": "Clip.2", + "metadata": {}, + "name": "Main088sh110", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 23.976024627685547, + "value": 82.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 23.976024627685547, + "value": 1937905.9905694576 + } + }, + "effects": [], + "markers": [ + { + "OTIO_SCHEMA": "Marker.2", + "metadata": { + "applieswhole": "1", + "hiero_source_type": "TrackItem", + "json_metadata": "{\"hiero_sub_products\": {\"io.ayon.creators.hiero.shot\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"shot\", \"productName\": \"shotMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.hiero.shot\", \"variant\": \"main\", \"folderPath\": \"/shots/088/Main088sh110\", \"task\": null, \"clip_index\": \"70C9FA86-76A5-A045-A004-3158FB3F27C5\", \"hierarchy\": \"shots/088\", \"folder\": \"shots\", \"episode\": \"404\", \"sequence\": \"088\", \"track\": \"Main\", \"shot\": \"sh110\", \"reviewableSource\": \"Reference\", \"sourceResolution\": false, \"workfileFrameStart\": 1009, \"handleStart\": 8, \"handleEnd\": 8, \"parents\": [{\"entity_type\": \"folder\", \"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"entity_type\": \"sequence\", \"folder_type\": \"sequence\", \"entity_name\": \"088\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"404\", \"sequence\": \"088\", \"track\": \"Main\"}, \"heroTrack\": true, \"uuid\": \"8b0d1db8-7094-48ba-b2cd-df0d43cfffda\", \"reviewTrack\": \"Reference\", \"review\": true, \"folderName\": \"Main088sh110\", \"label\": \"/shots/088/Main088sh110 shotMain\", \"newHierarchyIntegration\": true, \"instance_id\": \"f6b7f12c-f3a8-44fd-b4e4-acc63ed80bb1\", \"creator_attributes\": {\"workfileFrameStart\": 1009, \"handleStart\": 8, \"handleEnd\": 8, \"frameStart\": 1009, \"frameEnd\": 1091, \"clipIn\": 80, \"clipOut\": 161, \"clipDuration\": 82, \"sourceIn\": 8.0, \"sourceOut\": 89.0, \"fps\": \"from_selection\"}, \"publish_attributes\": {}}, \"io.ayon.creators.hiero.plate\": {\"id\": \"pyblish.avalon.instance\", \"productType\": \"plate\", \"productName\": \"plateMain\", \"active\": true, \"creator_identifier\": \"io.ayon.creators.hiero.plate\", \"variant\": \"Main\", \"folderPath\": \"/shots/088/Main088sh110\", \"task\": null, \"clip_index\": \"70C9FA86-76A5-A045-A004-3158FB3F27C5\", \"hierarchy\": \"shots/088\", \"folder\": \"shots\", \"episode\": \"404\", \"sequence\": \"088\", \"track\": \"Main\", \"shot\": \"sh110\", \"reviewableSource\": \"Reference\", \"sourceResolution\": false, \"workfileFrameStart\": 1009, \"handleStart\": 8, \"handleEnd\": 8, \"parents\": [{\"entity_type\": \"folder\", \"folder_type\": \"folder\", \"entity_name\": \"shots\"}, {\"entity_type\": \"sequence\", \"folder_type\": \"sequence\", \"entity_name\": \"088\"}], \"hierarchyData\": {\"folder\": \"shots\", \"episode\": \"404\", \"sequence\": \"088\", \"track\": \"Main\"}, \"heroTrack\": true, \"uuid\": \"8b0d1db8-7094-48ba-b2cd-df0d43cfffda\", \"reviewTrack\": \"Reference\", \"review\": true, \"folderName\": \"Main088sh110\", \"parent_instance_id\": \"f6b7f12c-f3a8-44fd-b4e4-acc63ed80bb1\", \"label\": \"/shots/088/Main088sh110 plateMain\", \"newHierarchyIntegration\": true, \"instance_id\": \"64b54c11-7ab1-45ef-b156-9ed5d5552b9b\", \"creator_attributes\": {\"parentInstance\": \"/shots/088/Main088sh110 shotMain\", \"review\": true, \"reviewableSource\": \"Reference\"}, \"publish_attributes\": {}}}, \"clip_index\": \"70C9FA86-76A5-A045-A004-3158FB3F27C5\"}", + "label": "AYONdata_6b797112", + "note": "AYON data container" + }, + "name": "AYONdata_6b797112", + "color": "RED", + "marked_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 23.976024627685547, + "value": 0.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 23.976024627685547, + "value": 0.0 + } + }, + "comment": "" + } + ], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ImageSequenceReference.1", + "metadata": { + "ayon.source.colorspace": "Input - Sony - Linear - Venice S-Gamut3.Cine", + "ayon.source.height": 2160, + "ayon.source.pixelAspect": 1.0, + "ayon.source.width": 4096, + "clip.properties.blendfunc": "0", + "clip.properties.colourspacename": "default", + "clip.properties.domainroot": "", + "clip.properties.enabled": "1", + "clip.properties.expanded": "1", + "clip.properties.opacity": "1", + "clip.properties.valuesource": "", + "foundry.source.audio": "", + "foundry.source.bitmapsize": "0", + "foundry.source.bitsperchannel": "0", + "foundry.source.channelformat": "integer", + "foundry.source.colourtransform": "Input - Sony - Linear - Venice S-Gamut3.Cine", + "foundry.source.duration": "98", + "foundry.source.filename": "409_083_0015.%04d.exr 1001-1098", + "foundry.source.filesize": "", + "foundry.source.fragments": "98", + "foundry.source.framerate": "23.98", + "foundry.source.fullpath": "", + "foundry.source.height": "2160", + "foundry.source.layers": "colour", + "foundry.source.path": "X:/prj/AYON_CIRCUIT_TEST/data/OBX_20240729_P159_DOG_409/EXR/409_083_0015/409_083_0015.%04d.exr 1001-1098", + "foundry.source.pixelAspect": "1", + "foundry.source.pixelAspectRatio": "", + "foundry.source.pixelformat": "RGBA (Float16) Open Color IO space: 368", + "foundry.source.reelID": "", + "foundry.source.resolution": "", + "foundry.source.samplerate": "Invalid", + "foundry.source.shortfilename": "409_083_0015.%04d.exr 1001-1098", + "foundry.source.shot": "", + "foundry.source.shotDate": "", + "foundry.source.startTC": "", + "foundry.source.starttime": "1001", + "foundry.source.timecode": "1937896", + "foundry.source.umid": "4b3e13b3-e465-4df4-cb1f-257091b63815", + "foundry.source.umidOriginator": "foundry.source.umid", + "foundry.source.width": "4096", + "foundry.timeline.colorSpace": "Input - Sony - Linear - Venice S-Gamut3.Cine", + "foundry.timeline.duration": "98", + "foundry.timeline.framerate": "23.98", + "foundry.timeline.outputformat": "", + "foundry.timeline.poster": "0", + "foundry.timeline.posterLayer": "colour", + "foundry.timeline.readParams": "AAAAAQAAAAAAAAAFAAAACmNvbG9yc3BhY2UAAAAFaW50MzIAAABqAAAAC2VkZ2VfcGl4ZWxzAAAABWludDMyAAAAAAAAABFpZ25vcmVfcGFydF9uYW1lcwAAAARib29sAAAAAAhub3ByZWZpeAAAAARib29sAAAAAB5vZmZzZXRfbmVnYXRpdmVfZGlzcGxheV93aW5kb3cAAAAEYm9vbAE=", + "foundry.timeline.samplerate": "Invalid", + "isSequence": true, + "media.exr.camera_camera_type": "AXS-R7", + "media.exr.camera_fps": "23.976", + "media.exr.camera_id": "MPC-3610 0010762 Version6.30", + "media.exr.camera_iso": "2500", + "media.exr.camera_lens_type": "Unknown", + "media.exr.camera_monitor_space": "OBX4_LUT_1_Night.cube", + "media.exr.camera_nd_filter": "1", + "media.exr.camera_roll_angle": "0.3", + "media.exr.camera_shutter_angle": "180.0", + "media.exr.camera_shutter_speed": "0.0208333", + "media.exr.camera_shutter_type": "Speed and Angle", + "media.exr.camera_sl_num": "00011434", + "media.exr.camera_tilt_angle": "-7.4", + "media.exr.camera_type": "Sony", + "media.exr.camera_white_kelvin": "3200", + "media.exr.channels": "B:{1 0 1 1},G:{1 0 1 1},R:{1 0 1 1}", + "media.exr.clip_details_codec": "F55_X-OCN_ST_4096_2160", + "media.exr.clip_details_pixel_aspect_ratio": "1", + "media.exr.clip_details_shot_frame_rate": "23.98p", + "media.exr.compression": "0", + "media.exr.compressionName": "none", + "media.exr.dataWindow": "0,0,4095,2159", + "media.exr.displayWindow": "0,0,4095,2159", + "media.exr.lineOrder": "0", + "media.exr.owner": "C272C010_240530HO", + "media.exr.pixelAspectRatio": "1", + "media.exr.screenWindowCenter": "0,0", + "media.exr.screenWindowWidth": "1", + "media.exr.tech_details_aspect_ratio": "1.8963", + "media.exr.tech_details_cdl_sat": "1", + "media.exr.tech_details_cdl_sop": "(1 1 1)(0 0 0)(1 1 1)", + "media.exr.tech_details_gamma_space": "R709 Video", + "media.exr.tech_details_par": "1", + "media.exr.type": "scanlineimage", + "media.input.bitsperchannel": "16-bit half float", + "media.input.ctime": "2024-07-30 18:51:38", + "media.input.filename": "X:/prj/AYON_CIRCUIT_TEST/data/OBX_20240729_P159_DOG_409/EXR/409_083_0015/409_083_0015.1001.exr", + "media.input.filereader": "exr", + "media.input.filesize": "53120020", + "media.input.frame": "1", + "media.input.frame_rate": "23.976", + "media.input.height": "2160", + "media.input.mtime": "2024-07-30 18:51:38", + "media.input.timecode": "22:25:45:16", + "media.input.width": "4096", + "padding": 4 + }, + "name": "", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 23.976, + "value": 98.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 23.976, + "value": 1937896.0 + } + }, + "available_image_bounds": null, + "target_url_base": "X:/prj/AYON_CIRCUIT_TEST/data/OBX_20240729_P159_DOG_409/EXR/409_083_0015\\", + "name_prefix": "409_083_0015.", + "name_suffix": ".exr", + "start_frame": 1001, + "frame_step": 1, + "rate": 23.976, + "frame_zero_padding": 4, + "missing_frame_policy": "error" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/test_extract_otio_review.py b/tests/client/ayon_core/pipeline/editorial/test_extract_otio_review.py index e1fbf514d4..8ad2e44b06 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_extract_otio_review.py +++ b/tests/client/ayon_core/pipeline/editorial/test_extract_otio_review.py @@ -252,48 +252,48 @@ def test_multiple_review_clips_no_gap(): '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 1199 C:/result/output.%04d.jpg', + '-start_number 1198 C:/result/output.%04d.jpg', '/path/to/ffmpeg -start_number 1000 -framerate 24.0 -i ' f'C:\\with_tc{os.sep}output.%04d.exr ' - '-start_number 1300 C:/result/output.%04d.jpg', + '-start_number 1299 C:/result/output.%04d.jpg', # Repeated 25fps tiff sequence multiple times till the end '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 1397 C:/result/output.%04d.jpg', + '-start_number 1395 C:/result/output.%04d.jpg', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 1498 C:/result/output.%04d.jpg', + '-start_number 1496 C:/result/output.%04d.jpg', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 1599 C:/result/output.%04d.jpg', + '-start_number 1597 C:/result/output.%04d.jpg', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 1700 C:/result/output.%04d.jpg', + '-start_number 1698 C:/result/output.%04d.jpg', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 1801 C:/result/output.%04d.jpg', + '-start_number 1799 C:/result/output.%04d.jpg', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 1902 C:/result/output.%04d.jpg', + '-start_number 1900 C:/result/output.%04d.jpg', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 2003 C:/result/output.%04d.jpg', + '-start_number 2001 C:/result/output.%04d.jpg', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 2104 C:/result/output.%04d.jpg', + '-start_number 2102 C:/result/output.%04d.jpg', '/path/to/ffmpeg -start_number 1000 -framerate 25.0 -i ' f'C:\\no_tc{os.sep}output.%04d.tif ' - '-start_number 2205 C:/result/output.%04d.jpg' + '-start_number 2203 C:/result/output.%04d.jpg' ] assert calls == expected diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index 7f9256c6d8..5a375e4499 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -64,6 +64,28 @@ def test_movie_embedded_tc_handle(): ) +def test_movie_23fps_qt_embedded_tc(): + """ + Movie clip (embedded timecode 1h) + available_range = 1937896-1937994 23.976fps + source_range = 1937905-1937987 23.97602462768554fps + """ + expected_data = { + 'mediaIn': 1009, + 'mediaOut': 1090, + 'handleStart': 8, + 'handleEnd': 8, + 'speed': 1.0 + } + + _check_expected_retimed_values( + "qt_23.976_embedded_long_tc.json", + expected_data, + handle_start=8, + handle_end=8, + ) + + def test_movie_retime_effect(): """ Movie clip (embedded timecode 1h) From c6ea24edcfc3d110c26a5c0084136035d3f1745a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:37:03 +0100 Subject: [PATCH 02/54] return StagingDir object instead of string --- client/ayon_core/pipeline/staging_dir.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/client/ayon_core/pipeline/staging_dir.py b/client/ayon_core/pipeline/staging_dir.py index 83878f17a2..b7ca1a2cd6 100644 --- a/client/ayon_core/pipeline/staging_dir.py +++ b/client/ayon_core/pipeline/staging_dir.py @@ -161,11 +161,15 @@ def get_staging_dir_info( ) if force_tmp_dir: - return get_temp_dir( - project_name=project_entity["name"], - anatomy=anatomy, - prefix=prefix, - suffix=suffix, + return StagingDir( + get_temp_dir( + project_name=project_entity["name"], + anatomy=anatomy, + prefix=prefix, + suffix=suffix, + ), + is_persistent=False, + is_custom=False ) # making few queries to database From 228a3c6f054388bad49dfeb020615e4b3eb2fdb8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:37:33 +0100 Subject: [PATCH 03/54] remove duplicated validation of template name --- client/ayon_core/pipeline/staging_dir.py | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/client/ayon_core/pipeline/staging_dir.py b/client/ayon_core/pipeline/staging_dir.py index b7ca1a2cd6..7f9ec85466 100644 --- a/client/ayon_core/pipeline/staging_dir.py +++ b/client/ayon_core/pipeline/staging_dir.py @@ -76,7 +76,6 @@ def get_staging_dir_config( # get template from template name template_name = profile["template_name"] - _validate_template_name(project_name, template_name, anatomy) template = anatomy.get_template_item("staging", template_name) @@ -93,19 +92,6 @@ def get_staging_dir_config( return {"template": template, "persistence": data_persistence} -def _validate_template_name(project_name, template_name, anatomy): - """Check that staging dir section with appropriate template exist. - - Raises: - ValueError - if misconfigured template - """ - if template_name not in anatomy.templates["staging"]: - raise ValueError( - f'Anatomy of project "{project_name}" does not have set' - f' "{template_name}" template key at Staging Dir category!' - ) - - def get_staging_dir_info( project_entity, folder_entity, From 2b1a04b5c0742320f1eb5adc501aa610e3d5e11c Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:38:44 +0100 Subject: [PATCH 04/54] added typehints --- .../pipeline/create/creator_plugins.py | 4 +- client/ayon_core/pipeline/staging_dir.py | 53 ++++++++++--------- 2 files changed, 30 insertions(+), 27 deletions(-) diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py index 6ccafe1bc7..28e9de20ee 100644 --- a/client/ayon_core/pipeline/create/creator_plugins.py +++ b/client/ayon_core/pipeline/create/creator_plugins.py @@ -15,7 +15,7 @@ from ayon_core.pipeline.plugin_discover import ( deregister_plugin, deregister_plugin_path ) -from ayon_core.pipeline import get_staging_dir_info +from ayon_core.pipeline.staging_dir import get_staging_dir_info, StagingDir from .constants import DEFAULT_VARIANT_VALUE from .product_name import get_product_name @@ -833,7 +833,7 @@ class Creator(BaseCreator): """ return self.pre_create_attr_defs - def get_staging_dir(self, instance): + def get_staging_dir(self, instance) -> Optional[StagingDir]: """Return the staging dir and persistence from instance. Args: diff --git a/client/ayon_core/pipeline/staging_dir.py b/client/ayon_core/pipeline/staging_dir.py index 7f9ec85466..7e0874fbef 100644 --- a/client/ayon_core/pipeline/staging_dir.py +++ b/client/ayon_core/pipeline/staging_dir.py @@ -1,3 +1,6 @@ +import logging +import warnings +from typing import Optional, Dict, Any from dataclasses import dataclass from ayon_core.lib import Logger, filter_profiles @@ -16,16 +19,16 @@ class StagingDir: def get_staging_dir_config( - project_name, - task_type, - task_name, - product_type, - product_name, - host_name, - project_settings=None, - anatomy=None, - log=None, -): + project_name: str, + task_type: Optional[str, None], + task_name: Optional[str, None], + product_type: str, + product_name: str, + host_name: str, + project_settings: Optional[Dict[str, Any]] = None, + anatomy: Optional[Anatomy] = None, + log: Optional[logging.Logger] = None, +) -> Optional[Dict[str, Any]]: """Get matching staging dir profile. Args: @@ -93,21 +96,21 @@ def get_staging_dir_config( def get_staging_dir_info( - project_entity, - folder_entity, - task_entity, - product_type, - product_name, - host_name, - anatomy=None, - project_settings=None, - template_data=None, - always_return_path=True, - force_tmp_dir=False, - logger=None, - prefix=None, - suffix=None, -): + project_entity: Dict[str, Any], + folder_entity: Optional[Dict[str, Any]], + task_entity: Optional[Dict[str, Any]], + product_type: str, + product_name: str, + host_name: str, + anatomy: Optional[Anatomy] = None, + project_settings: Optional[Dict[str, Any]] = None, + template_data: Optional[Dict[str, Any]] = None, + always_return_path: bool = True, + force_tmp_dir: bool = False, + logger: Optional[logging.Logger] = None, + prefix: Optional[str] = None, + suffix: Optional[str] = None, +) -> Optional[StagingDir]: """Get staging dir info data. If `force_temp` is set, staging dir will be created as tempdir. From 25f6ec241bbbbc3fb95e7770561a96e875341b1e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:39:57 +0100 Subject: [PATCH 05/54] added is_ prefix to StagingDir bools --- .../pipeline/create/creator_plugins.py | 2 +- client/ayon_core/pipeline/publish/lib.py | 4 +-- client/ayon_core/pipeline/staging_dir.py | 32 +++++++++++++++---- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py index 28e9de20ee..42e8e0b60f 100644 --- a/client/ayon_core/pipeline/create/creator_plugins.py +++ b/client/ayon_core/pipeline/create/creator_plugins.py @@ -915,7 +915,7 @@ class Creator(BaseCreator): instance.transient_data.update({ "stagingDir": staging_dir_path, - "stagingDir_persistent": staging_dir_info.persistent, + "stagingDir_persistent": staging_dir_info.is_persistent, }) self.log.info(f"Applied staging dir to instance: {staging_dir_path}") diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 586b90a3fd..ba0f846fe4 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -716,8 +716,8 @@ def get_instance_staging_dir(instance): os.makedirs(staging_dir_path, exist_ok=True) instance.data.update({ "stagingDir": staging_dir_path, - "stagingDir_persistent": staging_dir_info.persistent, - "stagingDir_custom": staging_dir_info.custom + "stagingDir_persistent": staging_dir_info.is_persistent, + "stagingDir_custom": staging_dir_info.is_custom }) return staging_dir_path diff --git a/client/ayon_core/pipeline/staging_dir.py b/client/ayon_core/pipeline/staging_dir.py index 7e0874fbef..2d94616faf 100644 --- a/client/ayon_core/pipeline/staging_dir.py +++ b/client/ayon_core/pipeline/staging_dir.py @@ -14,8 +14,28 @@ from .tempdir import get_temp_dir @dataclass class StagingDir: directory: str - persistent: bool - custom: bool # Whether the staging dir is a custom staging dir + is_persistent: bool + # Whether the staging dir is a custom staging dir + is_custom: bool + + def __setattr__(self, key, value): + if key == "persistent": + warnings.warn( + "'StagingDir.persistent' is deprecated." + " Use 'StagingDir.is_persistent' instead.", + DeprecationWarning + ) + key = "is_persistent" + super().__setattr__(key, value) + + @property + def persistent(self): + warnings.warn( + "'StagingDir.persistent' is deprecated." + " Use 'StagingDir.is_persistent' instead.", + DeprecationWarning + ) + return self.is_persistent def get_staging_dir_config( @@ -198,8 +218,8 @@ def get_staging_dir_info( dir_template = staging_dir_config["template"]["directory"] return StagingDir( dir_template.format_strict(ctx_data), - persistent=staging_dir_config["persistence"], - custom=True + is_persistent=staging_dir_config["persistence"], + is_custom=True ) # no config found but force an output @@ -211,8 +231,8 @@ def get_staging_dir_info( prefix=prefix, suffix=suffix, ), - persistent=False, - custom=False + is_persistent=False, + is_custom=False ) return None From 47fee3f54bd7d6b18849cdb59f8b5937988efa57 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 7 Jan 2025 15:41:58 +0100 Subject: [PATCH 06/54] change custom key to is_custom --- client/ayon_core/pipeline/publish/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index ba0f846fe4..40a9b47aba 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -717,7 +717,7 @@ def get_instance_staging_dir(instance): instance.data.update({ "stagingDir": staging_dir_path, "stagingDir_persistent": staging_dir_info.is_persistent, - "stagingDir_custom": staging_dir_info.is_custom + "stagingDir_is_custom": staging_dir_info.is_custom }) return staging_dir_path From 4641760bd17a232a0d6c7a24482060a46a475ba3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 8 Jan 2025 09:16:21 +0100 Subject: [PATCH 07/54] Initial changes for BorisFX Silhouette --- client/ayon_core/hooks/pre_add_last_workfile_arg.py | 3 ++- client/ayon_core/hooks/pre_ocio_hook.py | 3 ++- client/ayon_core/plugins/publish/validate_file_saved.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/hooks/pre_add_last_workfile_arg.py b/client/ayon_core/hooks/pre_add_last_workfile_arg.py index d5914c2352..a931fb0cbe 100644 --- a/client/ayon_core/hooks/pre_add_last_workfile_arg.py +++ b/client/ayon_core/hooks/pre_add_last_workfile_arg.py @@ -29,7 +29,8 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): "aftereffects", "wrap", "openrv", - "cinema4d" + "cinema4d", + "silhouette" } launch_types = {LaunchTypes.local} diff --git a/client/ayon_core/hooks/pre_ocio_hook.py b/client/ayon_core/hooks/pre_ocio_hook.py index 7406aa42cf..dd81cf053e 100644 --- a/client/ayon_core/hooks/pre_ocio_hook.py +++ b/client/ayon_core/hooks/pre_ocio_hook.py @@ -20,7 +20,8 @@ class OCIOEnvHook(PreLaunchHook): "hiero", "resolve", "openrv", - "cinema4d" + "cinema4d", + "silhouette" } launch_types = set() diff --git a/client/ayon_core/plugins/publish/validate_file_saved.py b/client/ayon_core/plugins/publish/validate_file_saved.py index f52998cef3..4f9e84aee0 100644 --- a/client/ayon_core/plugins/publish/validate_file_saved.py +++ b/client/ayon_core/plugins/publish/validate_file_saved.py @@ -37,7 +37,7 @@ class ValidateCurrentSaveFile(pyblish.api.ContextPlugin): label = "Validate File Saved" order = pyblish.api.ValidatorOrder - 0.1 hosts = ["fusion", "houdini", "max", "maya", "nuke", "substancepainter", - "cinema4d"] + "cinema4d", "silhouette"] actions = [SaveByVersionUpAction, ShowWorkfilesAction] def process(self, context): From be6aac6a72a76bb82300fe4ca612e5408bbea679 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 8 Jan 2025 09:17:21 +0100 Subject: [PATCH 08/54] Remove `TreeViewSpinner` with `QtSvg` dependency - The `TreeViewSpinner` widget was not used anywhere - The `QtSvg` dependency does not exist in BorisFX Silhouette so removing it was easiest to make Silhouette not error on this import --- client/ayon_core/tools/utils/views.py | 45 +-------------------------- 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/client/ayon_core/tools/utils/views.py b/client/ayon_core/tools/utils/views.py index b501f1ff11..d8ae94bf0c 100644 --- a/client/ayon_core/tools/utils/views.py +++ b/client/ayon_core/tools/utils/views.py @@ -1,7 +1,6 @@ -from ayon_core.resources import get_image_path from ayon_core.tools.flickcharm import FlickCharm -from qtpy import QtWidgets, QtCore, QtGui, QtSvg +from qtpy import QtWidgets, QtCore, QtGui class DeselectableTreeView(QtWidgets.QTreeView): @@ -19,48 +18,6 @@ class DeselectableTreeView(QtWidgets.QTreeView): QtWidgets.QTreeView.mousePressEvent(self, event) -class TreeViewSpinner(QtWidgets.QTreeView): - size = 160 - - def __init__(self, parent=None): - super(TreeViewSpinner, self).__init__(parent=parent) - - loading_image_path = get_image_path("spinner-200.svg") - - self.spinner = QtSvg.QSvgRenderer(loading_image_path) - - self.is_loading = False - self.is_empty = True - - def paint_loading(self, event): - rect = event.rect() - rect = QtCore.QRectF(rect.topLeft(), rect.bottomRight()) - rect.moveTo( - rect.x() + rect.width() / 2 - self.size / 2, - rect.y() + rect.height() / 2 - self.size / 2 - ) - rect.setSize(QtCore.QSizeF(self.size, self.size)) - painter = QtGui.QPainter(self.viewport()) - self.spinner.render(painter, rect) - - def paint_empty(self, event): - painter = QtGui.QPainter(self.viewport()) - rect = event.rect() - rect = QtCore.QRectF(rect.topLeft(), rect.bottomRight()) - qtext_opt = QtGui.QTextOption( - QtCore.Qt.AlignHCenter | QtCore.Qt.AlignVCenter - ) - painter.drawText(rect, "No Data", qtext_opt) - - def paintEvent(self, event): - if self.is_loading: - self.paint_loading(event) - elif self.is_empty: - self.paint_empty(event) - else: - super(TreeViewSpinner, self).paintEvent(event) - - class TreeView(QtWidgets.QTreeView): """Ultimate TreeView with flick charm and double click signals. From cb2758ef54c22da82d172fa54522b9b4bb1fd28e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 8 Jan 2025 13:03:03 +0100 Subject: [PATCH 09/54] Allow folders to be considered as workfiles in workfiles tool, because Silhouette projects are actually folders (with .sfx extension) --- client/ayon_core/tools/workfiles/models/workfiles.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/ayon_core/tools/workfiles/models/workfiles.py b/client/ayon_core/tools/workfiles/models/workfiles.py index a268a9bd0e..2c5ec230a6 100644 --- a/client/ayon_core/tools/workfiles/models/workfiles.py +++ b/client/ayon_core/tools/workfiles/models/workfiles.py @@ -185,8 +185,6 @@ class WorkareaModel: for filename in os.listdir(workdir): filepath = os.path.join(workdir, filename) - if not os.path.isfile(filepath): - continue ext = os.path.splitext(filename)[1].lower() if ext not in self._extensions: From 05ca2d42cd1b3306f81436c2da9d64f4285d2373 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Wed, 8 Jan 2025 14:23:05 +0100 Subject: [PATCH 10/54] fix typehint --- client/ayon_core/pipeline/staging_dir.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/pipeline/staging_dir.py b/client/ayon_core/pipeline/staging_dir.py index 2d94616faf..37d6b955e2 100644 --- a/client/ayon_core/pipeline/staging_dir.py +++ b/client/ayon_core/pipeline/staging_dir.py @@ -40,8 +40,8 @@ class StagingDir: def get_staging_dir_config( project_name: str, - task_type: Optional[str, None], - task_name: Optional[str, None], + task_type: Optional[str], + task_name: Optional[str], product_type: str, product_name: str, host_name: str, From 2c91d60d6df074ebcc74c1eced35fe191e6edeed Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 8 Jan 2025 16:41:06 +0100 Subject: [PATCH 11/54] Fix lint. --- client/ayon_core/pipeline/editorial.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index d71cf6c344..8ac4d906b1 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -228,7 +228,6 @@ def remap_range_on_file_sequence(otio_clip, otio_range): raise ValueError("Inconsistent range between clip and provided clip") source_range = otio_clip.source_range - source_range_rate = source_range.start_time.rate media_in = available_range.start_time available_range_start_frame = ( available_range.start_time.to_frames() @@ -254,8 +253,6 @@ def remap_range_on_file_sequence(otio_clip, otio_range): rate=available_range_rate, ).to_frames() - range_duration = otio_range.duration - frame_out = otio.opentime.RationalTime.from_frames( frame_in + otio_range.duration.to_frames() - 1, rate=available_range_rate, @@ -397,7 +394,10 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): src_duration = conformed_source_range.duration offset_in = otio.opentime.RationalTime(offset_in, rate=src_in.rate) - offset_duration = otio.opentime.RationalTime(offset_out, rate=src_duration.rate) + offset_duration = otio.opentime.RationalTime( + offset_out, + rate=src_duration.rate + ) trim_range = otio.opentime.TimeRange( start_time=src_in + offset_in, From 8db81098af5d15e20607c661ab510c6b6a6ef767 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 8 Jan 2025 17:55:13 +0100 Subject: [PATCH 12/54] Add comment --- client/ayon_core/tools/workfiles/models/workfiles.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/tools/workfiles/models/workfiles.py b/client/ayon_core/tools/workfiles/models/workfiles.py index 2c5ec230a6..c621a44937 100644 --- a/client/ayon_core/tools/workfiles/models/workfiles.py +++ b/client/ayon_core/tools/workfiles/models/workfiles.py @@ -184,6 +184,9 @@ class WorkareaModel: return items for filename in os.listdir(workdir): + # We want to support both files and folders. e.g. Silhoutte uses + # folders as its project files. So we do not check whether it is + # a file or not. filepath = os.path.join(workdir, filename) ext = os.path.splitext(filename)[1].lower() From ff2893dff04368f26c127080ef824f3bad95b2d1 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 8 Jan 2025 19:04:54 +0100 Subject: [PATCH 13/54] Fix remap with wrongly detected legacy image sequence. --- client/ayon_core/pipeline/editorial.py | 35 +++++++------ .../img_seq_24_to_23.976_no_legacy.json | 51 +++++++++++++++++++ .../test_media_range_with_retimes.py | 26 ++++++++++ 3 files changed, 96 insertions(+), 16 deletions(-) create mode 100644 tests/client/ayon_core/pipeline/editorial/resources/img_seq_24_to_23.976_no_legacy.json diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index 2928ef5f63..1d1859cbb8 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -231,10 +231,13 @@ def remap_range_on_file_sequence(otio_clip, in_out_range): # source range for image sequence. Following code maintain # backward-compatibility by adjusting media_in # while we are updating those. + conformed_src_in = source_range.start_time.rescaled_to( + available_range_rate + ) if ( is_clip_from_media_sequence(otio_clip) and available_range_start_frame == media_ref.start_frame - and source_range.start_time.to_frames() < media_ref.start_frame + and conformed_src_in.to_frames() < media_ref.start_frame ): media_in = 0 @@ -261,21 +264,6 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): media_ref = otio_clip.media_reference is_input_sequence = is_clip_from_media_sequence(otio_clip) - # Temporary. - # Some AYON custom OTIO exporter were implemented with relative - # source range for image sequence. Following code maintain - # backward-compatibility by adjusting available range - # while we are updating those. - if ( - is_input_sequence - and available_range.start_time.to_frames() == media_ref.start_frame - and source_range.start_time.to_frames() < media_ref.start_frame - ): - available_range = _ot.TimeRange( - _ot.RationalTime(0, rate=available_range_rate), - available_range.duration, - ) - # Conform source range bounds to available range rate # .e.g. embedded TC of (3600 sec/ 1h), duration 100 frames # @@ -320,6 +308,21 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): else: conformed_source_range = source_range + # Temporary. + # Some AYON custom OTIO exporter were implemented with relative + # source range for image sequence. Following code maintain + # backward-compatibility by adjusting available range + # while we are updating those. + if ( + is_input_sequence + and available_range.start_time.to_frames() == media_ref.start_frame + and conformed_source_range.start_time.to_frames() < media_ref.start_frame + ): + available_range = _ot.TimeRange( + _ot.RationalTime(0, rate=available_range_rate), + available_range.duration, + ) + # modifiers time_scalar = 1. offset_in = 0 diff --git a/tests/client/ayon_core/pipeline/editorial/resources/img_seq_24_to_23.976_no_legacy.json b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_24_to_23.976_no_legacy.json new file mode 100644 index 0000000000..108af0f2c1 --- /dev/null +++ b/tests/client/ayon_core/pipeline/editorial/resources/img_seq_24_to_23.976_no_legacy.json @@ -0,0 +1,51 @@ +{ + "OTIO_SCHEMA": "Clip.2", + "metadata": {}, + "name": "", + "source_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 23.976, + "value": 108.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 23.976, + "value": 883159.0 + } + }, + "effects": [], + "markers": [], + "enabled": true, + "media_references": { + "DEFAULT_MEDIA": { + "OTIO_SCHEMA": "ImageSequenceReference.1", + "metadata": {}, + "name": "", + "available_range": { + "OTIO_SCHEMA": "TimeRange.1", + "duration": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 755.0 + }, + "start_time": { + "OTIO_SCHEMA": "RationalTime.1", + "rate": 24.0, + "value": 883750.0 + } + }, + "available_image_bounds": null, + "target_url_base": "/mnt/jobs/yahoo_theDog_1132/IN/FOOTAGE/SCANS_LINEAR/Panasonic Rec 709 to ACESCG/Panasonic P2 /A001_S001_S001_T004/", + "name_prefix": "A001_S001_S001_T004.", + "name_suffix": ".exr", + "start_frame": 883750, + "frame_step": 1, + "rate": 1.0, + "frame_zero_padding": 0, + "missing_frame_policy": "error" + } + }, + "active_media_reference_key": "DEFAULT_MEDIA" +} \ No newline at end of file diff --git a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py index 7f9256c6d8..331732b6a4 100644 --- a/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py +++ b/tests/client/ayon_core/pipeline/editorial/test_media_range_with_retimes.py @@ -187,3 +187,29 @@ def test_img_sequence_conform_to_23_976fps(): handle_start=0, handle_end=8, ) + + +def test_img_sequence_conform_from_24_to_23_976fps(): + """ + Img sequence clip + available files = 883750-884504 24fps + source_range = 883159-883267 23.976fps + + This test ensures such entries do not trigger + the legacy Hiero export compatibility. + """ + expected_data = { + 'mediaIn': 884043, + 'mediaOut': 884150, + 'handleStart': 0, + 'handleEnd': 0, + 'speed': 1.0 + } + + _check_expected_retimed_values( + "img_seq_24_to_23.976_no_legacy.json", + expected_data, + handle_start=0, + handle_end=0, + ) + From 9fbef06cb0c30b1f11e685b9075bf0f53a2064ac Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Wed, 8 Jan 2025 19:16:10 +0100 Subject: [PATCH 14/54] Fix lint. --- client/ayon_core/pipeline/editorial.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/pipeline/editorial.py b/client/ayon_core/pipeline/editorial.py index 1d1859cbb8..a33f439651 100644 --- a/client/ayon_core/pipeline/editorial.py +++ b/client/ayon_core/pipeline/editorial.py @@ -316,7 +316,8 @@ def get_media_range_with_retimes(otio_clip, handle_start, handle_end): if ( is_input_sequence and available_range.start_time.to_frames() == media_ref.start_frame - and conformed_source_range.start_time.to_frames() < media_ref.start_frame + and conformed_source_range.start_time.to_frames() < + media_ref.start_frame ): available_range = _ot.TimeRange( _ot.RationalTime(0, rate=available_range_rate), From cd9929dfa192d080e5dcfea0f543104dd12cb4af Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 Jan 2025 17:01:50 +0100 Subject: [PATCH 15/54] set host name environment variable --- client/ayon_core/plugins/publish/collect_farm_env_variables.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/plugins/publish/collect_farm_env_variables.py b/client/ayon_core/plugins/publish/collect_farm_env_variables.py index cb52e5c32e..ee88985905 100644 --- a/client/ayon_core/plugins/publish/collect_farm_env_variables.py +++ b/client/ayon_core/plugins/publish/collect_farm_env_variables.py @@ -24,6 +24,7 @@ class CollectCoreJobEnvVars(pyblish.api.ContextPlugin): # NOTE we should use 'context.data["user"]' but that has higher # order. ("AYON_USERNAME", get_ayon_username()), + ("AYON_HOST_NAME", context.data["hostName"]), ): if value: self.log.debug(f"Setting job env: {key}: {value}") From 3fbb0f4dfb0ecfe46ca83929bbe00a9a92794d34 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 Jan 2025 18:18:24 +0100 Subject: [PATCH 16/54] metadata file does not require 'job' key to be filled --- client/ayon_core/plugins/publish/collect_rendered_files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/collect_rendered_files.py b/client/ayon_core/plugins/publish/collect_rendered_files.py index 42ba096d14..62b649cdd0 100644 --- a/client/ayon_core/plugins/publish/collect_rendered_files.py +++ b/client/ayon_core/plugins/publish/collect_rendered_files.py @@ -105,7 +105,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): instance.data.update(instance_data) # stash render job id for later validation - instance.data["render_job_id"] = data.get("job").get("_id") + instance.data["render_job_id"] = data.get("job", {}).get("_id") staging_dir_persistent = instance.data.get( "stagingDir_persistent", False ) From 8a7e11a1a4645bd886154c70eb9ec39b4ee2a831 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Thu, 9 Jan 2025 18:18:40 +0100 Subject: [PATCH 17/54] store metadata file content to 'publishJobMetadata' on instance --- client/ayon_core/plugins/publish/collect_rendered_files.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_rendered_files.py b/client/ayon_core/plugins/publish/collect_rendered_files.py index 62b649cdd0..deecf7ba24 100644 --- a/client/ayon_core/plugins/publish/collect_rendered_files.py +++ b/client/ayon_core/plugins/publish/collect_rendered_files.py @@ -93,8 +93,7 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): # now we can just add instances from json file and we are done any_staging_dir_persistent = False - for instance_data in data.get("instances"): - + for instance_data in data["instances"]: self.log.debug(" - processing instance for {}".format( instance_data.get("productName"))) instance = self._context.create_instance( @@ -105,6 +104,10 @@ class CollectRenderedFiles(pyblish.api.ContextPlugin): instance.data.update(instance_data) # stash render job id for later validation + instance.data["publishJobMetadata"] = data + # TODO remove 'render_job_id' here and rather use + # 'publishJobMetadata' where is needed. + # - this is deadline specific instance.data["render_job_id"] = data.get("job", {}).get("_id") staging_dir_persistent = instance.data.get( "stagingDir_persistent", False From eec3f61641b1ca21b9c6e2fdb225beffcb3c7827 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Jan 2025 15:56:18 +0100 Subject: [PATCH 18/54] Correctly emit signal --- client/ayon_core/tools/publisher/widgets/card_view_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 095a4eae7c..6bd4725371 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -197,7 +197,7 @@ class ConvertorItemsGroupWidget(BaseGroupWidget): else: widget = ConvertorItemCardWidget(item, self) widget.selected.connect(self._on_widget_selection) - widget.double_clicked(self.double_clicked) + widget.double_clicked.emit(self.double_clicked) self._widgets_by_id[item.id] = widget self._content_layout.insertWidget(widget_idx, widget) widget_idx += 1 From f4ea9aeacff690518822bd76459370fec8596d57 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 13 Jan 2025 16:01:11 +0100 Subject: [PATCH 19/54] Should be ``.connect` --- client/ayon_core/tools/publisher/widgets/card_view_widgets.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py index 6bd4725371..2f633b3149 100644 --- a/client/ayon_core/tools/publisher/widgets/card_view_widgets.py +++ b/client/ayon_core/tools/publisher/widgets/card_view_widgets.py @@ -197,7 +197,7 @@ class ConvertorItemsGroupWidget(BaseGroupWidget): else: widget = ConvertorItemCardWidget(item, self) widget.selected.connect(self._on_widget_selection) - widget.double_clicked.emit(self.double_clicked) + widget.double_clicked.connect(self.double_clicked) self._widgets_by_id[item.id] = widget self._content_layout.insertWidget(widget_idx, widget) widget_idx += 1 From 1d23ca6633d5ff8d85ebeda066de7a621a60e869 Mon Sep 17 00:00:00 2001 From: MustafaJafar Date: Mon, 13 Jan 2025 23:35:43 +0200 Subject: [PATCH 20/54] update default order to match values in `CollectUSDLayerContributions` --- server/settings/publish_plugins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 8893b00e23..1bf2e853cf 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -1008,8 +1008,8 @@ DEFAULT_PUBLISH_VALUES = { {"name": "model", "order": 100}, {"name": "assembly", "order": 150}, {"name": "groom", "order": 175}, - {"name": "look", "order": 300}, - {"name": "rig", "order": 100}, + {"name": "look", "order": 200}, + {"name": "rig", "order": 300}, # Shot layers {"name": "layout", "order": 200}, {"name": "animation", "order": 300}, From 11b00e61050b3d4027efe7fbedc14111bce87451 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 14 Jan 2025 08:27:36 +0000 Subject: [PATCH 21/54] [Automated] Add generated package files from main --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index 2417897a47..0a985e2829 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.12+dev" +__version__ = "1.0.13" diff --git a/package.py b/package.py index 8ade5ceeed..dae6a9ca0a 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.12+dev" +version = "1.0.13" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index b8d6a5a537..78edf5c2e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.12+dev" +version = "1.0.13" description = "" authors = ["Ynput Team "] readme = "README.md" From e3b9bfa29d91efc380bde4389783abc0aa9c868d Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 14 Jan 2025 08:28:17 +0000 Subject: [PATCH 22/54] [Automated] Update version in package.py for develop --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index 0a985e2829..b0ada09e7c 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.13" +__version__ = "1.0.13+dev" diff --git a/package.py b/package.py index dae6a9ca0a..03b69d4c5c 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.13" +version = "1.0.13+dev" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index 78edf5c2e3..5e42aa7093 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.13" +version = "1.0.13+dev" description = "" authors = ["Ynput Team "] readme = "README.md" From 3d9459f3c9002cba98652bf05968959deee5cfe8 Mon Sep 17 00:00:00 2001 From: "robin@ynput.io" Date: Tue, 14 Jan 2025 14:17:51 +0100 Subject: [PATCH 23/54] Report stagingDir_is_custom to apply_staging_dir. --- client/ayon_core/pipeline/create/creator_plugins.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/pipeline/create/creator_plugins.py b/client/ayon_core/pipeline/create/creator_plugins.py index 42e8e0b60f..445b41cb4b 100644 --- a/client/ayon_core/pipeline/create/creator_plugins.py +++ b/client/ayon_core/pipeline/create/creator_plugins.py @@ -916,6 +916,7 @@ class Creator(BaseCreator): instance.transient_data.update({ "stagingDir": staging_dir_path, "stagingDir_persistent": staging_dir_info.is_persistent, + "stagingDir_is_custom": staging_dir_info.is_custom, }) self.log.info(f"Applied staging dir to instance: {staging_dir_path}") From 2be4d7c2e8b7ff7d9d133c60d3eb0141640ba3f3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Jan 2025 16:03:30 +0100 Subject: [PATCH 24/54] Disallow work area save as and browse if not in active task --- client/ayon_core/tools/workfiles/widgets/files_widget.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/client/ayon_core/tools/workfiles/widgets/files_widget.py b/client/ayon_core/tools/workfiles/widgets/files_widget.py index 16f0b6fce3..c5b5047c83 100644 --- a/client/ayon_core/tools/workfiles/widgets/files_widget.py +++ b/client/ayon_core/tools/workfiles/widgets/files_widget.py @@ -136,6 +136,8 @@ class FilesWidget(QtWidgets.QWidget): # Initial setup workarea_btn_open.setEnabled(False) + workarea_btn_browse.setEnabled(False) + workarea_btn_save.setEnabled(False) published_btn_copy_n_open.setEnabled(False) published_btn_change_context.setEnabled(False) published_btn_cancel.setVisible(False) @@ -264,6 +266,9 @@ class FilesWidget(QtWidgets.QWidget): def _on_workarea_path_changed(self, event): valid_path = event["path"] is not None self._workarea_btn_open.setEnabled(valid_path) + valid_task = self._selected_task_id is not None + self._workarea_btn_save.setEnabled(valid_task) + self._workarea_btn_browse.setEnabled(valid_task) # ------------------------------------------------------------- # Published workfiles From 44da46411c765d92611692e870dd6ef9322bfd1a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 14 Jan 2025 16:18:42 +0100 Subject: [PATCH 25/54] Move logic to correct location --- client/ayon_core/tools/workfiles/widgets/files_widget.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/tools/workfiles/widgets/files_widget.py b/client/ayon_core/tools/workfiles/widgets/files_widget.py index c5b5047c83..dbe5966c31 100644 --- a/client/ayon_core/tools/workfiles/widgets/files_widget.py +++ b/client/ayon_core/tools/workfiles/widgets/files_widget.py @@ -266,9 +266,6 @@ class FilesWidget(QtWidgets.QWidget): def _on_workarea_path_changed(self, event): valid_path = event["path"] is not None self._workarea_btn_open.setEnabled(valid_path) - valid_task = self._selected_task_id is not None - self._workarea_btn_save.setEnabled(valid_task) - self._workarea_btn_browse.setEnabled(valid_task) # ------------------------------------------------------------- # Published workfiles @@ -283,8 +280,9 @@ class FilesWidget(QtWidgets.QWidget): self._published_btn_change_context.setEnabled(enabled) def _update_workarea_btns_state(self): - enabled = self._is_save_enabled + enabled = self._is_save_enabled and self._valid_selected_context self._workarea_btn_save.setEnabled(enabled) + self._workarea_btn_browse.setEnabled(self._valid_selected_context) def _on_published_repre_changed(self, event): self._valid_representation_id = event["representation_id"] is not None @@ -299,6 +297,7 @@ class FilesWidget(QtWidgets.QWidget): and self._selected_task_id is not None ) self._update_published_btns_state() + self._update_workarea_btns_state() def _on_published_save_clicked(self): result = self._exec_save_as_dialog() From b185972a954f7111591576b90ee3436a7b775bf2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 15 Jan 2025 14:02:20 +0100 Subject: [PATCH 26/54] Avoid database queries when collecting managed staging dir --- client/ayon_core/pipeline/publish/lib.py | 1 + client/ayon_core/pipeline/staging_dir.py | 5 ++++- client/ayon_core/pipeline/template_data.py | 13 ++++++++++--- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/client/ayon_core/pipeline/publish/lib.py b/client/ayon_core/pipeline/publish/lib.py index 40a9b47aba..25495ed38b 100644 --- a/client/ayon_core/pipeline/publish/lib.py +++ b/client/ayon_core/pipeline/publish/lib.py @@ -708,6 +708,7 @@ def get_instance_staging_dir(instance): project_settings=context.data["project_settings"], template_data=template_data, always_return_path=True, + username=context.data["user"], ) staging_dir_path = staging_dir_info.directory diff --git a/client/ayon_core/pipeline/staging_dir.py b/client/ayon_core/pipeline/staging_dir.py index 37d6b955e2..2004096bd0 100644 --- a/client/ayon_core/pipeline/staging_dir.py +++ b/client/ayon_core/pipeline/staging_dir.py @@ -130,6 +130,7 @@ def get_staging_dir_info( logger: Optional[logging.Logger] = None, prefix: Optional[str] = None, suffix: Optional[str] = None, + username: Optional[str] = None, ) -> Optional[StagingDir]: """Get staging dir info data. @@ -183,7 +184,9 @@ def get_staging_dir_info( # making few queries to database ctx_data = get_template_data( - project_entity, folder_entity, task_entity, host_name + project_entity, folder_entity, task_entity, host_name, + settings=project_settings, + username=username ) # add additional data diff --git a/client/ayon_core/pipeline/template_data.py b/client/ayon_core/pipeline/template_data.py index c7aa46fd62..0a95a98be8 100644 --- a/client/ayon_core/pipeline/template_data.py +++ b/client/ayon_core/pipeline/template_data.py @@ -4,7 +4,7 @@ from ayon_core.settings import get_studio_settings from ayon_core.lib.local_settings import get_ayon_username -def get_general_template_data(settings=None): +def get_general_template_data(settings=None, username=None): """General template data based on system settings or machine. Output contains formatting keys: @@ -14,17 +14,22 @@ def get_general_template_data(settings=None): Args: settings (Dict[str, Any]): Studio or project settings. + username (Optional[str]): AYON Username. """ if not settings: settings = get_studio_settings() + + if username is None: + username = get_ayon_username() + core_settings = settings["core"] return { "studio": { "name": core_settings["studio_name"], "code": core_settings["studio_code"] }, - "user": get_ayon_username() + "user": username } @@ -145,6 +150,7 @@ def get_template_data( task_entity=None, host_name=None, settings=None, + username=None ): """Prepare data for templates filling from entered documents and info. @@ -167,12 +173,13 @@ def get_template_data( host_name (Optional[str]): Used to fill '{app}' key. settings (Union[Dict, None]): Prepared studio or project settings. They're queried if not passed (may be slower). + username (Optional[str]): AYON Username. Returns: Dict[str, Any]: Data prepared for filling workdir template. """ - template_data = get_general_template_data(settings) + template_data = get_general_template_data(settings, username=username) template_data.update(get_project_template_data(project_entity)) if folder_entity: template_data.update(get_folder_template_data( From 199ed55357fedcb81e84a32c4bc67ecf64478778 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 16 Jan 2025 15:48:02 +0800 Subject: [PATCH 27/54] add substance designer into OCIO and last workfile pre-launch hook --- client/ayon_core/hooks/pre_add_last_workfile_arg.py | 1 + client/ayon_core/hooks/pre_ocio_hook.py | 1 + client/ayon_core/pipeline/farm/pyblish_functions.py | 2 ++ 3 files changed, 4 insertions(+) diff --git a/client/ayon_core/hooks/pre_add_last_workfile_arg.py b/client/ayon_core/hooks/pre_add_last_workfile_arg.py index d5914c2352..daea8c5502 100644 --- a/client/ayon_core/hooks/pre_add_last_workfile_arg.py +++ b/client/ayon_core/hooks/pre_add_last_workfile_arg.py @@ -26,6 +26,7 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): "photoshop", "tvpaint", "substancepainter", + "substancedesigner", "aftereffects", "wrap", "openrv", diff --git a/client/ayon_core/hooks/pre_ocio_hook.py b/client/ayon_core/hooks/pre_ocio_hook.py index 7406aa42cf..78fc8c78de 100644 --- a/client/ayon_core/hooks/pre_ocio_hook.py +++ b/client/ayon_core/hooks/pre_ocio_hook.py @@ -10,6 +10,7 @@ class OCIOEnvHook(PreLaunchHook): order = 0 hosts = { "substancepainter", + "substancedesigner", "fusion", "blender", "aftereffects", diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index e48d99602e..16174a47a9 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -935,7 +935,9 @@ def _collect_expected_files_for_aov(files): ValueError: If there are multiple collections. """ + print(f"files: {files}") cols, rem = clique.assemble(files) + print(cols) # we shouldn't have any reminders. And if we do, it should # be just one item for single frame renders. if not cols and rem: From 03790a5f0c158f531315a806a2bf5a4dedbad0c1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 16 Jan 2025 23:39:26 +0100 Subject: [PATCH 28/54] Fix too big chunky icons in BorisFX Silhouette - This change was also tested in Fusion (where Qt runs completely separately and this addition didn't result in a visual difference there) --- client/ayon_core/style/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/style/style.css b/client/ayon_core/style/style.css index bd96a3aeed..fa26605354 100644 --- a/client/ayon_core/style/style.css +++ b/client/ayon_core/style/style.css @@ -23,6 +23,9 @@ Enabled vs Disabled logic in most of stylesheets font-family: "Noto Sans"; font-weight: 450; outline: none; + + /* Fix icons in BorisFX Silhouette */ + icon-size: 16px; } QWidget { From 43f8764b7fb7d2481927f4bc55e91122765f28e9 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 17 Jan 2025 00:01:54 +0100 Subject: [PATCH 29/54] Reduce margins on Workfiles tool due to nested layouts --- client/ayon_core/tools/workfiles/widgets/window.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/tools/workfiles/widgets/window.py b/client/ayon_core/tools/workfiles/widgets/window.py index 8bcff66f50..1649a059cb 100644 --- a/client/ayon_core/tools/workfiles/widgets/window.py +++ b/client/ayon_core/tools/workfiles/widgets/window.py @@ -113,6 +113,7 @@ class WorkfilesToolWindow(QtWidgets.QWidget): main_layout = QtWidgets.QHBoxLayout(self) main_layout.addWidget(pages_widget, 1) + main_layout.setContentsMargins(0, 0, 0, 0) overlay_messages_widget = MessageOverlayObject(self) overlay_invalid_host = InvalidHostOverlay(self) From 17e20a2d0f007843f04a91873cbb7b1f11c323b2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 17 Jan 2025 00:07:14 +0100 Subject: [PATCH 30/54] Set the icon size in the stylesheet to avoid too big clunky icons in BorisFX Silhouette. The sizes appeared the same in Fusion and Maya with this added to the stylesheet (no changes there) --- client/ayon_core/style/style.css | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ayon_core/style/style.css b/client/ayon_core/style/style.css index bd96a3aeed..fa26605354 100644 --- a/client/ayon_core/style/style.css +++ b/client/ayon_core/style/style.css @@ -23,6 +23,9 @@ Enabled vs Disabled logic in most of stylesheets font-family: "Noto Sans"; font-weight: 450; outline: none; + + /* Fix icons in BorisFX Silhouette */ + icon-size: 16px; } QWidget { From e0133f54b66473aa8133e2759d6d95219e552eeb Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 17 Jan 2025 16:15:21 +0800 Subject: [PATCH 31/54] remove unrelated code --- client/ayon_core/pipeline/farm/pyblish_functions.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/client/ayon_core/pipeline/farm/pyblish_functions.py b/client/ayon_core/pipeline/farm/pyblish_functions.py index 16174a47a9..e48d99602e 100644 --- a/client/ayon_core/pipeline/farm/pyblish_functions.py +++ b/client/ayon_core/pipeline/farm/pyblish_functions.py @@ -935,9 +935,7 @@ def _collect_expected_files_for_aov(files): ValueError: If there are multiple collections. """ - print(f"files: {files}") cols, rem = clique.assemble(files) - print(cols) # we shouldn't have any reminders. And if we do, it should # be just one item for single frame renders. if not cols and rem: From c166bf0514952bcef3e722d68f4c67cac47c0a92 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Mon, 20 Jan 2025 21:55:34 +0800 Subject: [PATCH 32/54] add sbsar as the families --- client/ayon_core/plugins/publish/collect_resources_path.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/collect_resources_path.py b/client/ayon_core/plugins/publish/collect_resources_path.py index 7a80d0054c..2e5b296228 100644 --- a/client/ayon_core/plugins/publish/collect_resources_path.py +++ b/client/ayon_core/plugins/publish/collect_resources_path.py @@ -66,7 +66,8 @@ class CollectResourcesPath(pyblish.api.InstancePlugin): "yeticacheUE", "tycache", "usd", - "oxrig" + "oxrig", + "sbsar", ] def process(self, instance): From 81bb74c7ea0e0c46a17f8bc16e54f0044fb81066 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 20 Jan 2025 18:07:18 +0100 Subject: [PATCH 33/54] Apply suggestions from code review Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/hooks/pre_add_last_workfile_arg.py | 2 +- client/ayon_core/hooks/pre_ocio_hook.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/ayon_core/hooks/pre_add_last_workfile_arg.py b/client/ayon_core/hooks/pre_add_last_workfile_arg.py index a931fb0cbe..5b10357632 100644 --- a/client/ayon_core/hooks/pre_add_last_workfile_arg.py +++ b/client/ayon_core/hooks/pre_add_last_workfile_arg.py @@ -30,7 +30,7 @@ class AddLastWorkfileToLaunchArgs(PreLaunchHook): "wrap", "openrv", "cinema4d", - "silhouette" + "silhouette", } launch_types = {LaunchTypes.local} diff --git a/client/ayon_core/hooks/pre_ocio_hook.py b/client/ayon_core/hooks/pre_ocio_hook.py index dd81cf053e..8765dbd0b2 100644 --- a/client/ayon_core/hooks/pre_ocio_hook.py +++ b/client/ayon_core/hooks/pre_ocio_hook.py @@ -21,7 +21,7 @@ class OCIOEnvHook(PreLaunchHook): "resolve", "openrv", "cinema4d", - "silhouette" + "silhouette", } launch_types = set() From 827651a4a669fefe8823db7095315d5ebb96599d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 21 Jan 2025 12:20:50 +0100 Subject: [PATCH 34/54] Update client/ayon_core/style/style.css Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- client/ayon_core/style/style.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/style/style.css b/client/ayon_core/style/style.css index fa26605354..a5e54453cc 100644 --- a/client/ayon_core/style/style.css +++ b/client/ayon_core/style/style.css @@ -24,7 +24,7 @@ Enabled vs Disabled logic in most of stylesheets font-weight: 450; outline: none; - /* Fix icons in BorisFX Silhouette */ + /* Define icon size to fix size issues for most of DCCs */ icon-size: 16px; } From 41045c1092fce14032a09a2a4dd4608eddd8a71f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 21 Jan 2025 12:52:02 +0100 Subject: [PATCH 35/54] Add missing argument in docstring --- client/ayon_core/pipeline/staging_dir.py | 1 + 1 file changed, 1 insertion(+) diff --git a/client/ayon_core/pipeline/staging_dir.py b/client/ayon_core/pipeline/staging_dir.py index 2004096bd0..1cb2979415 100644 --- a/client/ayon_core/pipeline/staging_dir.py +++ b/client/ayon_core/pipeline/staging_dir.py @@ -158,6 +158,7 @@ def get_staging_dir_info( logger (Optional[logging.Logger]): Logger instance. prefix (Optional[str]) Optional prefix for staging dir name. suffix (Optional[str]): Optional suffix for staging dir name. + username (Optional[str]): AYON Username. Returns: Optional[StagingDir]: Staging dir info data From 2712260d08c9d8d777dfea9dbe7cfc4ec735f644 Mon Sep 17 00:00:00 2001 From: Ynbot Date: Tue, 21 Jan 2025 14:49:22 +0000 Subject: [PATCH 36/54] [Automated] Add generated package files from main --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index b0ada09e7c..e90676d739 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.13+dev" +__version__ = "1.0.14" diff --git a/package.py b/package.py index 03b69d4c5c..bb38b431b1 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.13+dev" +version = "1.0.14" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index 5e42aa7093..2496e3fa34 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.13+dev" +version = "1.0.14" description = "" authors = ["Ynput Team "] readme = "README.md" From dec3dd2178130a0f197aeb4147c6eb1e290f84fb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 21 Jan 2025 14:50:15 +0000 Subject: [PATCH 37/54] chore(): update bug report / version --- .github/ISSUE_TEMPLATE/bug_report.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 54f5d68b98..c0ab04abef 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -35,6 +35,20 @@ body: label: Version description: What version are you running? Look to AYON Tray options: + - 1.0.14 + - 1.0.13 + - 1.0.12 + - 1.0.11 + - 1.0.10 + - 1.0.9 + - 1.0.8 + - 1.0.7 + - 1.0.6 + - 1.0.5 + - 1.0.4 + - 1.0.3 + - 1.0.2 + - 1.0.1 - 1.0.0 - 0.4.4 - 0.4.3 From cdf7b743e8e8f38a3ccdf22ab0ec3ecb126e835d Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 21 Jan 2025 15:56:12 +0100 Subject: [PATCH 38/54] bump version to '1.0.15-dev' --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index e90676d739..2775cb606a 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.14" +__version__ = "1.0.15-dev" diff --git a/package.py b/package.py index bb38b431b1..af3342f3f2 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.14" +version = "1.0.15-dev" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index 2496e3fa34..e040ce986f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.14" +version = "1.0.15-dev" description = "" authors = ["Ynput Team "] readme = "README.md" From 2569015b9fe1a5b0baf5ee44539417ff0e678b3d Mon Sep 17 00:00:00 2001 From: Liam Hoflay Date: Wed, 22 Jan 2025 16:44:45 +0000 Subject: [PATCH 39/54] changing localhost for 0.0.0.0 in client/ayon_core/tools/tray/webserver/server.py --- client/ayon_core/tools/tray/webserver/server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/tools/tray/webserver/server.py b/client/ayon_core/tools/tray/webserver/server.py index d2a9b0fc6b..d9b3b857f5 100644 --- a/client/ayon_core/tools/tray/webserver/server.py +++ b/client/ayon_core/tools/tray/webserver/server.py @@ -28,7 +28,7 @@ def find_free_port( exclude_ports (list, tuple, set): List of ports that won't be checked form entered range. host (str): Host where will check for free ports. Set to - "localhost" by default. + "0.0.0.0" by default. """ if port_from is None: port_from = 8079 @@ -42,7 +42,7 @@ def find_free_port( # Default host is localhost but it is possible to look for other hosts if host is None: - host = "localhost" + host = "0.0.0.0" found_port = None while True: @@ -78,7 +78,7 @@ class WebServerManager: self._log = None self.port = port or 8079 - self.host = host or "localhost" + self.host = host or "0.0.0.0" self.on_stop_callbacks = [] From 27145add3aefa23bf34040f7e307307bd473fd00 Mon Sep 17 00:00:00 2001 From: Liam Hoflay Date: Thu, 23 Jan 2025 10:25:13 +0000 Subject: [PATCH 40/54] making 127.0.0.1 rather than 0.0.0.0 --- client/ayon_core/tools/tray/webserver/server.py | 6 +++--- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/ayon_core/tools/tray/webserver/server.py b/client/ayon_core/tools/tray/webserver/server.py index d9b3b857f5..70d1fc8c0f 100644 --- a/client/ayon_core/tools/tray/webserver/server.py +++ b/client/ayon_core/tools/tray/webserver/server.py @@ -28,7 +28,7 @@ def find_free_port( exclude_ports (list, tuple, set): List of ports that won't be checked form entered range. host (str): Host where will check for free ports. Set to - "0.0.0.0" by default. + "127.0.0.1" by default. """ if port_from is None: port_from = 8079 @@ -42,7 +42,7 @@ def find_free_port( # Default host is localhost but it is possible to look for other hosts if host is None: - host = "0.0.0.0" + host = "127.0.0.1" found_port = None while True: @@ -78,7 +78,7 @@ class WebServerManager: self._log = None self.port = port or 8079 - self.host = host or "0.0.0.0" + self.host = host or "127.0.0.1" self.on_stop_callbacks = [] diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index 2775cb606a..fabbb4f0e9 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.15-dev" +__version__ = "1.0.14-bk.1" diff --git a/package.py b/package.py index af3342f3f2..db5e0b80dc 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.15-dev" +version = "1.0.14-bk.1" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index e040ce986f..dc550a1eda 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.15-dev" +version = "1.0.14-bk.1" description = "" authors = ["Ynput Team "] readme = "README.md" From 6708abaa43c1e22ea2435f450c03ca5c2e6cd500 Mon Sep 17 00:00:00 2001 From: Liam Hoflay Date: Thu, 23 Jan 2025 11:19:48 +0000 Subject: [PATCH 41/54] version revert --- client/ayon_core/version.py | 2 +- package.py | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/version.py b/client/ayon_core/version.py index fabbb4f0e9..2775cb606a 100644 --- a/client/ayon_core/version.py +++ b/client/ayon_core/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring AYON addon 'core' version.""" -__version__ = "1.0.14-bk.1" +__version__ = "1.0.15-dev" diff --git a/package.py b/package.py index db5e0b80dc..af3342f3f2 100644 --- a/package.py +++ b/package.py @@ -1,6 +1,6 @@ name = "core" title = "Core" -version = "1.0.14-bk.1" +version = "1.0.15-dev" client_dir = "ayon_core" diff --git a/pyproject.toml b/pyproject.toml index dc550a1eda..e040ce986f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,7 +5,7 @@ [tool.poetry] name = "ayon-core" -version = "1.0.14-bk.1" +version = "1.0.15-dev" description = "" authors = ["Ynput Team "] readme = "README.md" From ffec35f69f140a426ee3198ef9de6d01a7c11840 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 23 Jan 2025 17:26:32 +0100 Subject: [PATCH 42/54] Add Silhouette host to defaults of few validators --- server/settings/publish_plugins.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 1bf2e853cf..2cca9cd66e 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -1033,7 +1033,8 @@ DEFAULT_PUBLISH_VALUES = { "maya", "nuke", "photoshop", - "substancepainter" + "substancepainter", + "silhouette" ], "enabled": True, "optional": False, @@ -1053,7 +1054,8 @@ DEFAULT_PUBLISH_VALUES = { "harmony", "photoshop", "aftereffects", - "fusion" + "fusion", + "silhouette" ], "enabled": True, "optional": True, From 509c6a84d3004882cdff9b45e8cb31b18465db7e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:24:02 +0100 Subject: [PATCH 43/54] set minimum size of content widget to be able to stretch it's size --- client/ayon_core/tools/publisher/widgets/report_page.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/client/ayon_core/tools/publisher/widgets/report_page.py b/client/ayon_core/tools/publisher/widgets/report_page.py index b7afcf470a..2684f85356 100644 --- a/client/ayon_core/tools/publisher/widgets/report_page.py +++ b/client/ayon_core/tools/publisher/widgets/report_page.py @@ -1332,6 +1332,8 @@ class InstancesLogsView(QtWidgets.QFrame): content_widget = QtWidgets.QWidget(content_wrap_widget) content_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + content_widget.setMinimumSize(80, 80) + content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(8, 8, 8, 8) content_layout.setSpacing(15) From c2f3d8b114ecd93871029144bc81a77ad841758e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 24 Jan 2025 14:31:06 +0100 Subject: [PATCH 44/54] use 'AYON_INSTANCE_ID' by default instead of 'AVALON_' --- client/ayon_core/pipeline/create/legacy_create.py | 4 ++-- client/ayon_core/pipeline/create/structures.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/pipeline/create/legacy_create.py b/client/ayon_core/pipeline/create/legacy_create.py index ec9b23ac62..f6427d9bd1 100644 --- a/client/ayon_core/pipeline/create/legacy_create.py +++ b/client/ayon_core/pipeline/create/legacy_create.py @@ -9,7 +9,7 @@ import os import logging import collections -from ayon_core.pipeline.constants import AVALON_INSTANCE_ID +from ayon_core.pipeline.constants import AYON_INSTANCE_ID from .product_name import get_product_name @@ -34,7 +34,7 @@ class LegacyCreator: # Default data self.data = collections.OrderedDict() # TODO use 'AYON_INSTANCE_ID' when all hosts support it - self.data["id"] = AVALON_INSTANCE_ID + self.data["id"] = AYON_INSTANCE_ID self.data["productType"] = self.product_type self.data["folderPath"] = folder_path self.data["productName"] = name diff --git a/client/ayon_core/pipeline/create/structures.py b/client/ayon_core/pipeline/create/structures.py index a1a4d5f8ef..a45e053cca 100644 --- a/client/ayon_core/pipeline/create/structures.py +++ b/client/ayon_core/pipeline/create/structures.py @@ -492,7 +492,7 @@ class CreatedInstance: item_id = data.get("id") # TODO use only 'AYON_INSTANCE_ID' when all hosts support it if item_id not in {AYON_INSTANCE_ID, AVALON_INSTANCE_ID}: - item_id = AVALON_INSTANCE_ID + item_id = AYON_INSTANCE_ID self._data["id"] = item_id self._data["productType"] = product_type self._data["productName"] = product_name From c380ebfedf03e77e4744df42db75eb527ae7663f Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 24 Jan 2025 17:09:03 +0100 Subject: [PATCH 45/54] use 'QTextEdit' for log message --- client/ayon_core/style/style.css | 2 + .../tools/publisher/widgets/report_page.py | 58 ++++++++++++++++--- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/client/ayon_core/style/style.css b/client/ayon_core/style/style.css index a5e54453cc..0e19702d53 100644 --- a/client/ayon_core/style/style.css +++ b/client/ayon_core/style/style.css @@ -1171,6 +1171,8 @@ ValidationArtistMessage QLabel { #PublishLogMessage { font-family: "Noto Sans Mono"; + border: none; + padding: 0; } #PublishInstanceLogsLabel { diff --git a/client/ayon_core/tools/publisher/widgets/report_page.py b/client/ayon_core/tools/publisher/widgets/report_page.py index 2684f85356..58c78c0b06 100644 --- a/client/ayon_core/tools/publisher/widgets/report_page.py +++ b/client/ayon_core/tools/publisher/widgets/report_page.py @@ -1117,6 +1117,54 @@ class LogIconFrame(QtWidgets.QFrame): painter.end() +class LogItemMessage(QtWidgets.QTextEdit): + def __init__(self, msg, parent): + super().__init__(msg, parent) + + self.setObjectName("PublishLogMessage") + self.setReadOnly(True) + self.setFrameStyle(QtWidgets.QFrame.NoFrame) + self.setLineWidth(0) + self.setMidLineWidth(0) + pal = self.palette() + pal.setColor(QtGui.QPalette.Base, QtCore.Qt.transparent) + self.setPalette(pal) + self.setContentsMargins(0, 0, 0, 0) + viewport = self.viewport() + viewport.setContentsMargins(0, 0, 0, 0) + + self.setTextInteractionFlags( + QtCore.Qt.TextBrowserInteraction) + self.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor)) + self.setLineWrapMode(QtWidgets.QTextEdit.WidgetWidth) + self.setWordWrapMode( + QtGui.QTextOption.WrapMode.WrapAtWordBoundaryOrAnywhere + ) + self.setSizePolicy( + QtWidgets.QSizePolicy.Preferred, + QtWidgets.QSizePolicy.Maximum + ) + document = self.document() + document.documentLayout().documentSizeChanged.connect( + self._adjust_minimum_size + ) + document.setDocumentMargin(0.0) + self._height = None + + def _adjust_minimum_size(self, size): + self._height = size.height() + (2 * self.frameWidth()) + self.updateGeometry() + + def sizeHint(self): + size = super().sizeHint() + if self._height is not None: + size.setHeight(self._height) + return size + + def minimumSizeHint(self): + return self.sizeHint() + + class LogItemWidget(QtWidgets.QWidget): log_level_to_flag = { 10: LOG_DEBUG_VISIBLE, @@ -1132,12 +1180,7 @@ class LogItemWidget(QtWidgets.QWidget): type_flag, level_n = self._get_log_info(log) icon_label = LogIconFrame( self, log["type"], level_n, log.get("is_validation_error")) - message_label = QtWidgets.QLabel(log["msg"].rstrip(), self) - message_label.setObjectName("PublishLogMessage") - message_label.setTextInteractionFlags( - QtCore.Qt.TextBrowserInteraction) - message_label.setCursor(QtGui.QCursor(QtCore.Qt.IBeamCursor)) - message_label.setWordWrap(True) + message_label = LogItemMessage(log["msg"].rstrip(), self) main_layout = QtWidgets.QHBoxLayout(self) main_layout.setContentsMargins(0, 0, 0, 0) @@ -1290,6 +1333,7 @@ class InstanceLogsWidget(QtWidgets.QWidget): label_widget = QtWidgets.QLabel(instance.label, self) label_widget.setObjectName("PublishInstanceLogsLabel") + label_widget.setWordWrap(True) logs_grid = LogsWithIconsView(instance.logs, self) layout = QtWidgets.QVBoxLayout(self) @@ -1329,10 +1373,10 @@ class InstancesLogsView(QtWidgets.QFrame): content_wrap_widget = QtWidgets.QWidget(scroll_area) content_wrap_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + content_wrap_widget.setMinimumWidth(80) content_widget = QtWidgets.QWidget(content_wrap_widget) content_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) - content_widget.setMinimumSize(80, 80) content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(8, 8, 8, 8) From 22546fd9c695a5e02a61252414a153b76ee9921a Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Fri, 24 Jan 2025 17:10:33 +0100 Subject: [PATCH 46/54] remove whitespaces --- client/ayon_core/tools/publisher/widgets/report_page.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/report_page.py b/client/ayon_core/tools/publisher/widgets/report_page.py index 58c78c0b06..c0c1120fc9 100644 --- a/client/ayon_core/tools/publisher/widgets/report_page.py +++ b/client/ayon_core/tools/publisher/widgets/report_page.py @@ -1164,7 +1164,7 @@ class LogItemMessage(QtWidgets.QTextEdit): def minimumSizeHint(self): return self.sizeHint() - + class LogItemWidget(QtWidgets.QWidget): log_level_to_flag = { 10: LOG_DEBUG_VISIBLE, From 521e50619ecda3e8001b1280c2e46952d8c3a2de Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 27 Jan 2025 13:55:52 +0100 Subject: [PATCH 47/54] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Jakub Ježek --- server/settings/publish_plugins.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/settings/publish_plugins.py b/server/settings/publish_plugins.py index 2cca9cd66e..18e7d67f90 100644 --- a/server/settings/publish_plugins.py +++ b/server/settings/publish_plugins.py @@ -1034,7 +1034,7 @@ DEFAULT_PUBLISH_VALUES = { "nuke", "photoshop", "substancepainter", - "silhouette" + "silhouette", ], "enabled": True, "optional": False, @@ -1055,7 +1055,7 @@ DEFAULT_PUBLISH_VALUES = { "photoshop", "aftereffects", "fusion", - "silhouette" + "silhouette", ], "enabled": True, "optional": True, From 83b7c3d4429244126d984dc20bc7bcf5a663ab3e Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Mon, 27 Jan 2025 14:00:54 +0100 Subject: [PATCH 48/54] fix new-line character --- client/ayon_core/tools/publisher/widgets/report_page.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/tools/publisher/widgets/report_page.py b/client/ayon_core/tools/publisher/widgets/report_page.py index c0c1120fc9..1e46e7e52c 100644 --- a/client/ayon_core/tools/publisher/widgets/report_page.py +++ b/client/ayon_core/tools/publisher/widgets/report_page.py @@ -1119,7 +1119,10 @@ class LogIconFrame(QtWidgets.QFrame): class LogItemMessage(QtWidgets.QTextEdit): def __init__(self, msg, parent): - super().__init__(msg, parent) + super().__init__(parent) + + # Set as plain text to propagate new line characters + self.setPlainText(msg) self.setObjectName("PublishLogMessage") self.setReadOnly(True) From 5684c941deb0f2f62cf71f69667f786217ded612 Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 28 Jan 2025 11:51:17 +0100 Subject: [PATCH 49/54] 'CreatedInstance' allows to pass in transient data --- .../ayon_core/pipeline/create/structures.py | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/client/ayon_core/pipeline/create/structures.py b/client/ayon_core/pipeline/create/structures.py index a45e053cca..17bb85b720 100644 --- a/client/ayon_core/pipeline/create/structures.py +++ b/client/ayon_core/pipeline/create/structures.py @@ -1,6 +1,7 @@ import copy import collections from uuid import uuid4 +import typing from typing import Optional, Dict, List, Any from ayon_core.lib.attribute_definitions import ( @@ -17,6 +18,9 @@ from ayon_core.pipeline import ( from .exceptions import ImmutableKeyError from .changes import TrackChangesItem +if typing.TYPE_CHECKING: + from .creator_plugins import BaseCreator + class ConvertorItem: """Item representing convertor plugin. @@ -444,10 +448,11 @@ class CreatedInstance: def __init__( self, - product_type, - product_name, - data, - creator, + product_type: str, + product_name: str, + data: Dict[str, Any], + creator: "BaseCreator", + transient_data: Optional[Dict[str, Any]] = None, ): self._creator = creator creator_identifier = creator.identifier @@ -462,7 +467,9 @@ class CreatedInstance: self._members = [] # Data that can be used for lifetime of object - self._transient_data = {} + if transient_data is None: + transient_data = {} + self._transient_data = transient_data # Create a copy of passed data to avoid changing them on the fly data = copy.deepcopy(data or {}) @@ -787,16 +794,26 @@ class CreatedInstance: self._create_context.instance_create_attr_defs_changed(self.id) @classmethod - def from_existing(cls, instance_data, creator): + def from_existing( + cls, + instance_data: Dict[str, Any], + creator: "BaseCreator", + transient_data: Optional[Dict[str, Any]] = None, + ) -> "CreatedInstance": """Convert instance data from workfile to CreatedInstance. Args: instance_data (Dict[str, Any]): Data in a structure ready for 'CreatedInstance' object. creator (BaseCreator): Creator plugin which is creating the - instance of for which the instance belong. - """ + instance of for which the instance belongs. + transient_data (Optional[dict[str, Any]]): Instance transient + data. + Returns: + CreatedInstance: Instance object. + + """ instance_data = copy.deepcopy(instance_data) product_type = instance_data.get("productType") @@ -809,7 +826,11 @@ class CreatedInstance: product_name = instance_data.get("subset") return cls( - product_type, product_name, instance_data, creator + product_type, + product_name, + instance_data, + creator, + transient_data=transient_data, ) def attribute_value_changed(self, key, changes): From be5eb08d2b6ff0ee9dae3592e8c157835e414b26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Thu, 30 Jan 2025 10:57:21 +0100 Subject: [PATCH 50/54] Update client/ayon_core/plugins/publish/collect_otio_frame_ranges.py Co-authored-by: Robin De Lillo --- .../ayon_core/plugins/publish/collect_otio_frame_ranges.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py b/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py index 917b9ad206..5d306138eb 100644 --- a/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py +++ b/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py @@ -123,10 +123,7 @@ class CollectOtioRanges(pyblish.api.InstancePlugin): otio_src_range_handles = otio_range_with_handles(otio_src_range, instance) # Get source available start frame - src_starting_from = otio.opentime.to_frames( - otio_available_range.start_time, - otio_available_range.start_time.rate - ) + src_starting_from = otio_available_range.to_frames() # Convert to frames src_start, src_end = otio_range_to_frame_range(otio_src_range) From f91baa0e1ebd17d171b23eaeac9096569654a71f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Je=C5=BEek?= Date: Thu, 30 Jan 2025 10:58:04 +0100 Subject: [PATCH 51/54] Update client/ayon_core/plugins/publish/collect_otio_frame_ranges.py Co-authored-by: Robin De Lillo --- client/ayon_core/plugins/publish/collect_otio_frame_ranges.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py b/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py index 5d306138eb..0187858be8 100644 --- a/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py +++ b/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py @@ -101,8 +101,7 @@ class CollectOtioRanges(pyblish.api.InstancePlugin): tl_start_h, tl_end_h = otio_range_to_frame_range(otio_tl_range_handles) frame_start = workfile_start - frame_end = frame_start + otio.opentime.to_frames( - otio_tl_range.duration, otio_tl_range.duration.rate) - 1 + frame_end = frame_start + otio_tl_range.duration.to_frames() - 1 data = { "frameStart": frame_start, From 888e81fac81f2bbe6fe759c9e34b0787aca26e4b Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Jan 2025 11:12:37 +0100 Subject: [PATCH 52/54] Fix OTIO frame range handling and add compatibility - Added backward compatibility for Hiero OTIO exporter. - Implemented rounding for floating rates to avoid precision issues. - Rescaled source ranges based on available range rates. - Updated calculations for source start, end, and handles. --- .../publish/collect_otio_frame_ranges.py | 48 ++++++++++++++----- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py b/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py index 0187858be8..f93301d0f6 100644 --- a/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py +++ b/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py @@ -119,20 +119,46 @@ class CollectOtioRanges(pyblish.api.InstancePlugin): # Get source ranges otio_src_range = otio_clip.source_range otio_available_range = otio_clip.available_range() - otio_src_range_handles = otio_range_with_handles(otio_src_range, instance) - # Get source available start frame - src_starting_from = otio_available_range.to_frames() - - # Convert to frames - src_start, src_end = otio_range_to_frame_range(otio_src_range) - src_start_h, src_end_h = otio_range_to_frame_range(otio_src_range_handles) + # Backward-compatibility for Hiero OTIO exporter. + # NTSC compatibility might introduce floating rates, when these are + # not exactly the same (23.976 vs 23.976024627685547) + # this will cause precision issue in computation. + # Currently round to 2 decimals for comparison, + # but this should always rescale after that. + rounded_av_rate = round(otio_available_range.start_time.rate, 2) + rounded_src_rate = round(otio_src_range.start_time.rate, 2) + if rounded_av_rate != rounded_src_rate: + conformed_src_in = otio_src_range.start_time.rescaled_to( + otio_available_range.start_time.rate + ) + conformed_src_duration = otio_src_range.duration.rescaled_to( + otio_available_range.duration.rate + ) + conformed_source_range = otio.opentime.TimeRange( + start_time=conformed_src_in, + duration=conformed_src_duration + ) + else: + conformed_source_range = otio_src_range + source_start = conformed_source_range.start_time + source_end = source_start + conformed_source_range.duration + handle_start = otio.opentime.RationalTime( + instance.data.get("handleStart", 0), + source_start.rate + ) + handle_end = otio.opentime.RationalTime( + instance.data.get("handleEnd", 0), + source_start.rate + ) + source_start_h = source_start - handle_start + source_end_h = source_end + handle_end data = { - "sourceStart": src_starting_from + src_start, - "sourceEnd": src_starting_from + src_end - 1, - "sourceStartH": src_starting_from + src_start_h, - "sourceEndH": src_starting_from + src_end_h - 1, + "sourceStart": source_start.to_frames(), + "sourceEnd": source_end.to_frames() - 1, + "sourceStartH": source_start_h.to_frames(), + "sourceEndH": source_end_h.to_frames() - 1, } instance.data.update(data) self.log.debug(f"Added source ranges: {pformat(data)}") From de82d8b60cb944148bba9047e89c04196de5658a Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Jan 2025 11:26:46 +0100 Subject: [PATCH 53/54] Add handling for clips without available ranges - Added a check for available range in OTIO clips. - Improved logging to inform when a clip has no available range. - Adjusted source range collection logic based on availability. --- .../plugins/publish/collect_otio_frame_ranges.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py b/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py index f93301d0f6..34c12a1cf3 100644 --- a/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py +++ b/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py @@ -80,8 +80,18 @@ class CollectOtioRanges(pyblish.api.InstancePlugin): if "workfileFrameStart" in instance.data: self._collect_timeline_ranges(instance, otio_clip) + has_available_range = False + # Traypublisher Simple or Advanced editorial publishing is + # working with otio clips which are having no available range + # because they are not having any media references. + try: + otio_clip.available_range() + has_available_range = True + except otio._otio.CannotComputeAvailableRangeError: + self.log.info("Clip has no available range") + # Collect source ranges if clip has available range - if hasattr(otio_clip, 'available_range') and otio_clip.available_range(): + if has_available_range: self._collect_source_ranges(instance, otio_clip) # Handle retimed ranges if source duration is available From 22769ef460a8e0a9c9972fc6a4fa90d4e7f2d549 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 30 Jan 2025 15:05:32 +0100 Subject: [PATCH 54/54] Refactor frame range handling for clarity - Improved readability by breaking long lines into multiple lines. - Removed unused variable related to shot duration from source. - Cleaned up code structure in the frame range collection process. --- .../ayon_core/plugins/publish/collect_otio_frame_ranges.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py b/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py index 34c12a1cf3..8a8af29ca9 100644 --- a/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py +++ b/client/ayon_core/plugins/publish/collect_otio_frame_ranges.py @@ -104,11 +104,13 @@ class CollectOtioRanges(pyblish.api.InstancePlugin): # Get timeline ranges otio_tl_range = otio_clip.range_in_parent() - otio_tl_range_handles = otio_range_with_handles(otio_tl_range, instance) + otio_tl_range_handles = otio_range_with_handles( + otio_tl_range, instance) # Convert to frames tl_start, tl_end = otio_range_to_frame_range(otio_tl_range) - tl_start_h, tl_end_h = otio_range_to_frame_range(otio_tl_range_handles) + tl_start_h, tl_end_h = otio_range_to_frame_range( + otio_tl_range_handles) frame_start = workfile_start frame_end = frame_start + otio_tl_range.duration.to_frames() - 1 @@ -175,7 +177,6 @@ class CollectOtioRanges(pyblish.api.InstancePlugin): def _collect_retimed_ranges(self, instance, otio_clip): """Handle retimed clip frame ranges.""" - workfile_source_duration = instance.data.get("shotDurationFromSource") frame_start = instance.data["frameStart"] # Handle retimed clip frame range