diff --git a/.github/workflows/project_actions.yml b/.github/workflows/project_actions.yml index 769f40e0b8..1e1a1441f7 100644 --- a/.github/workflows/project_actions.yml +++ b/.github/workflows/project_actions.yml @@ -25,8 +25,8 @@ jobs: name: pr_size_label runs-on: ubuntu-latest if: | - ${{(github.event_name == 'pull_request' && github.event.action == 'synchronize') - || (github.event_name == 'pull_request' && github.event.action == 'assigned')}} + ${{(github.event_name == 'pull_request' && github.event.action == 'assigned') + || (github.event_name == 'pull_request' && github.event.action == 'opened')}} steps: - name: Add size label @@ -49,7 +49,7 @@ jobs: name: pr_branch_label runs-on: ubuntu-latest if: | - ${{(github.event_name == 'pull_request' && github.event.action == 'synchronize') + ${{(github.event_name == 'pull_request' && github.event.action == 'assigned') || (github.event_name == 'pull_request' && github.event.action == 'opened')}} steps: - name: Label PRs - Branch name detection @@ -61,11 +61,12 @@ jobs: name: pr_globe_label runs-on: ubuntu-latest if: | - ${{(github.event_name == 'pull_request' && github.event.action == 'synchronize') + ${{(github.event_name == 'pull_request' && github.event.action == 'assigned') || (github.event_name == 'pull_request' && github.event.action == 'opened')}} steps: - name: Label PRs - Globe detection - uses: actions/labeler@v4 + uses: actions/labeler@v4.0.3 with: repo-token: ${{ secrets.YNPUT_BOT_TOKEN }} configuration-path: ".github/pr-glob-labeler.yml" + sync-labels: false diff --git a/openpype/hosts/harmony/api/TB_sceneOpened.js b/openpype/hosts/harmony/api/TB_sceneOpened.js index 6614f14f9e..a284a6ec5c 100644 --- a/openpype/hosts/harmony/api/TB_sceneOpened.js +++ b/openpype/hosts/harmony/api/TB_sceneOpened.js @@ -476,6 +476,25 @@ function start() { action.triggered.connect(self.onSubsetManage); } + /** + * Set scene settings from DB to the scene + */ + self.onSetSceneSettings = function() { + app.avalonClient.send( + { + "module": "openpype.hosts.harmony.api", + "method": "ensure_scene_settings", + "args": [] + }, + false + ); + }; + // add Set Scene Settings + if (app.avalonMenu == null) { + action = menu.addAction('Set Scene Settings...'); + action.triggered.connect(self.onSetSceneSettings); + } + /** * Show Experimental dialog */ diff --git a/openpype/hosts/harmony/api/pipeline.py b/openpype/hosts/harmony/api/pipeline.py index 686770b64e..285ee806a1 100644 --- a/openpype/hosts/harmony/api/pipeline.py +++ b/openpype/hosts/harmony/api/pipeline.py @@ -142,7 +142,7 @@ def application_launch(event): harmony.send({"script": script}) inject_avalon_js() - ensure_scene_settings() + # ensure_scene_settings() check_inventory() diff --git a/openpype/hosts/harmony/plugins/publish/extract_render.py b/openpype/hosts/harmony/plugins/publish/extract_render.py index c29864bb28..38b09902c1 100644 --- a/openpype/hosts/harmony/plugins/publish/extract_render.py +++ b/openpype/hosts/harmony/plugins/publish/extract_render.py @@ -25,8 +25,9 @@ class ExtractRender(pyblish.api.InstancePlugin): application_path = instance.context.data.get("applicationPath") scene_path = instance.context.data.get("scenePath") frame_rate = instance.context.data.get("frameRate") - frame_start = instance.context.data.get("frameStart") - frame_end = instance.context.data.get("frameEnd") + # real value from timeline + frame_start = instance.context.data.get("frameStartHandle") + frame_end = instance.context.data.get("frameEndHandle") audio_path = instance.context.data.get("audioPath") if audio_path and os.path.exists(audio_path): @@ -55,9 +56,13 @@ class ExtractRender(pyblish.api.InstancePlugin): # Execute rendering. Ignoring error cause Harmony returns error code # always. - self.log.info(f"running [ {application_path} -batch {scene_path}") + + args = [application_path, "-batch", + "-frames", str(frame_start), str(frame_end), + "-scene", scene_path] + self.log.info(f"running [ {application_path} {' '.join(args)}") proc = subprocess.Popen( - [application_path, "-batch", scene_path], + args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.PIPE diff --git a/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py b/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py index 936533abd6..6e4c6955e4 100644 --- a/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py +++ b/openpype/hosts/harmony/plugins/publish/validate_scene_settings.py @@ -60,7 +60,8 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): # which is available on 'context.data["assetEntity"]' # - the same approach can be used in 'ValidateSceneSettingsRepair' expected_settings = harmony.get_asset_settings() - self.log.info("scene settings from DB:".format(expected_settings)) + self.log.info("scene settings from DB:{}".format(expected_settings)) + expected_settings.pop("entityType") # not useful for the validation expected_settings = _update_frames(dict.copy(expected_settings)) expected_settings["frameEndHandle"] = expected_settings["frameEnd"] +\ @@ -68,21 +69,32 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): if (any(re.search(pattern, os.getenv('AVALON_TASK')) for pattern in self.skip_resolution_check)): + self.log.info("Skipping resolution check because of " + "task name and pattern {}".format( + self.skip_resolution_check)) expected_settings.pop("resolutionWidth") expected_settings.pop("resolutionHeight") - entity_type = expected_settings.get("entityType") - if (any(re.search(pattern, entity_type) + if (any(re.search(pattern, os.getenv('AVALON_TASK')) for pattern in self.skip_timelines_check)): + self.log.info("Skipping frames check because of " + "task name and pattern {}".format( + self.skip_timelines_check)) expected_settings.pop('frameStart', None) expected_settings.pop('frameEnd', None) - - expected_settings.pop("entityType") # not useful after the check + expected_settings.pop('frameStartHandle', None) + expected_settings.pop('frameEndHandle', None) asset_name = instance.context.data['anatomyData']['asset'] if any(re.search(pattern, asset_name) for pattern in self.frame_check_filter): - expected_settings.pop("frameEnd") + self.log.info("Skipping frames check because of " + "task name and pattern {}".format( + self.frame_check_filter)) + expected_settings.pop('frameStart', None) + expected_settings.pop('frameEnd', None) + expected_settings.pop('frameStartHandle', None) + expected_settings.pop('frameEndHandle', None) # handle case where ftrack uses only two decimal places # 23.976023976023978 vs. 23.98 @@ -99,6 +111,7 @@ class ValidateSceneSettings(pyblish.api.InstancePlugin): "frameEnd": instance.context.data["frameEnd"], "handleStart": instance.context.data.get("handleStart"), "handleEnd": instance.context.data.get("handleEnd"), + "frameStartHandle": instance.context.data.get("frameStartHandle"), "frameEndHandle": instance.context.data.get("frameEndHandle"), "resolutionWidth": instance.context.data.get("resolutionWidth"), "resolutionHeight": instance.context.data.get("resolutionHeight"), diff --git a/openpype/hosts/houdini/plugins/publish/collect_current_file.py b/openpype/hosts/houdini/plugins/publish/collect_current_file.py index 9cca07fdc7..caf679f98b 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_current_file.py +++ b/openpype/hosts/houdini/plugins/publish/collect_current_file.py @@ -1,7 +1,6 @@ import os import hou -from openpype.pipeline import legacy_io import pyblish.api @@ -11,7 +10,7 @@ class CollectHoudiniCurrentFile(pyblish.api.InstancePlugin): order = pyblish.api.CollectorOrder - 0.01 label = "Houdini Current File" hosts = ["houdini"] - family = ["workfile"] + families = ["workfile"] def process(self, instance): """Inject the current working file""" @@ -21,7 +20,7 @@ class CollectHoudiniCurrentFile(pyblish.api.InstancePlugin): # By default, Houdini will even point a new scene to a path. # However if the file is not saved at all and does not exist, # we assume the user never set it. - filepath = "" + current_file = "" elif os.path.basename(current_file) == "untitled.hip": # Due to even a new file being called 'untitled.hip' we are unable diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index aa1e501578..1a62e7dbc3 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2478,8 +2478,8 @@ def load_capture_preset(data=None): float(value[2]) / 255 ] disp_options[key] = value - else: - disp_options['displayGradient'] = True + elif key == "displayGradient": + disp_options[key] = value options['display_options'] = disp_options diff --git a/openpype/hosts/maya/plugins/load/load_reference.py b/openpype/hosts/maya/plugins/load/load_reference.py index d93702a16d..82c15ab899 100644 --- a/openpype/hosts/maya/plugins/load/load_reference.py +++ b/openpype/hosts/maya/plugins/load/load_reference.py @@ -1,4 +1,6 @@ import os +import difflib +import contextlib from maya import cmds from openpype.settings import get_project_settings @@ -8,7 +10,82 @@ from openpype.pipeline.create import ( get_legacy_creator_by_name, ) import openpype.hosts.maya.api.plugin -from openpype.hosts.maya.api.lib import maintained_selection +from openpype.hosts.maya.api.lib import ( + maintained_selection, + get_container_members +) + + +@contextlib.contextmanager +def preserve_modelpanel_cameras(container, log=None): + """Preserve camera members of container in the modelPanels. + + This is used to ensure a camera remains in the modelPanels after updating + to a new version. + + """ + + # Get the modelPanels that used the old camera + members = get_container_members(container) + old_cameras = set(cmds.ls(members, type="camera", long=True)) + if not old_cameras: + # No need to manage anything + yield + return + + panel_cameras = {} + for panel in cmds.getPanel(type="modelPanel"): + cam = cmds.ls(cmds.modelPanel(panel, query=True, camera=True), + long=True) + + # Often but not always maya returns the transform from the + # modelPanel as opposed to the camera shape, so we convert it + # to explicitly be the camera shape + if cmds.nodeType(cam) != "camera": + cam = cmds.listRelatives(cam, + children=True, + fullPath=True, + type="camera")[0] + if cam in old_cameras: + panel_cameras[panel] = cam + + if not panel_cameras: + # No need to manage anything + yield + return + + try: + yield + finally: + new_members = get_container_members(container) + new_cameras = set(cmds.ls(new_members, type="camera", long=True)) + if not new_cameras: + return + + for panel, cam_name in panel_cameras.items(): + new_camera = None + if cam_name in new_cameras: + new_camera = cam_name + elif len(new_cameras) == 1: + new_camera = next(iter(new_cameras)) + else: + # Multiple cameras in the updated container but not an exact + # match detected by name. Find the closest match + matches = difflib.get_close_matches(word=cam_name, + possibilities=new_cameras, + n=1) + if matches: + new_camera = matches[0] # best match + if log: + log.info("Camera in '{}' restored with " + "closest match camera: {} (before: {})" + .format(panel, new_camera, cam_name)) + + if not new_camera: + # Unable to find the camera to re-apply in the modelpanel + continue + + cmds.modelPanel(panel, edit=True, camera=new_camera) class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): @@ -68,6 +145,9 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): new_nodes = (list(set(nodes) - set(shapes))) + # if there are cameras, try to lock their transforms + self._lock_camera_transforms(new_nodes) + current_namespace = pm.namespaceInfo(currentNamespace=True) if current_namespace != ":": @@ -136,6 +216,15 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): def switch(self, container, representation): self.update(container, representation) + def update(self, container, representation): + with preserve_modelpanel_cameras(container, log=self.log): + super(ReferenceLoader, self).update(container, representation) + + # We also want to lock camera transforms on any new cameras in the + # reference or for a camera which might have changed names. + members = get_container_members(container) + self._lock_camera_transforms(members) + def _post_process_rig(self, name, namespace, context, options): output = next((node for node in self if @@ -168,3 +257,18 @@ class ReferenceLoader(openpype.hosts.maya.api.plugin.ReferenceLoader): options={"useSelection": True}, data={"dependencies": dependency} ) + + def _lock_camera_transforms(self, nodes): + cameras = cmds.ls(nodes, type="camera") + if not cameras: + return + + # Check the Maya version, lockTransform has been introduced since + # Maya 2016.5 Ext 2 + version = int(cmds.about(version=True)) + if version >= 2016: + for camera in cameras: + cmds.camera(camera, edit=True, lockTransform=True) + else: + self.log.warning("This version of Maya does not support locking of" + " transforms of cameras.") diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index e914eb29f9..fda053e6e6 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -795,6 +795,7 @@ "quality": 95 }, "Display Options": { + "override_display": true, "background": [ 125, 125, @@ -813,7 +814,7 @@ 125, 255 ], - "override_display": true + "displayGradient": true }, "Generic": { "isolate_view": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 416e530db2..a4a986bad8 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -48,7 +48,11 @@ "type": "label", "label": "Display Options" }, - + { + "type": "boolean", + "key": "override_display", + "label": "Override display options" + }, { "type": "color", "key": "background", @@ -66,8 +70,8 @@ }, { "type": "boolean", - "key": "override_display", - "label": "Override display options" + "key": "displayGradient", + "label": "Display background gradient" } ] },