diff --git a/.github/workflows/assign_pr_to_project.yml b/.github/workflows/assign_pr_to_project.yml index 4bb3d1742c..e61d281c2a 100644 --- a/.github/workflows/assign_pr_to_project.yml +++ b/.github/workflows/assign_pr_to_project.yml @@ -1,15 +1,48 @@ name: 🔸Auto assign pr on: + workflow_dispatch: + inputs: + pr_number: + type: string + description: "Run workflow for this PR number" + required: true + project_id: + type: string + description: "Github Project Number" + required: true + default: "16" pull_request: types: - opened +env: + GH_TOKEN: ${{ github.token }} + jobs: + get-pr-repo: + runs-on: ubuntu-latest + outputs: + pr_repo_name: ${{ steps.get-repo-name.outputs.repo_name || github.event.pull_request.head.repo.full_name }} + + # INFO `github.event.pull_request.head.repo.full_name` is not available on manual triggered (dispatched) runs + steps: + - name: Get PR repo name + if: ${{ github.event_name == 'workflow_dispatch' }} + id: get-repo-name + run: | + repo_name=$(gh pr view ${{ inputs.pr_number }} --json headRepository,headRepositoryOwner --repo ${{ github.repository }} | jq -r '.headRepositoryOwner.login + "/" + .headRepository.name') + echo "repo_name=$repo_name" >> $GITHUB_OUTPUT + auto-assign-pr: + needs: + - get-pr-repo + if: ${{ needs.get-pr-repo.outputs.pr_repo_name == github.repository }} uses: ynput/ops-repo-automation/.github/workflows/pr_to_project.yml@main with: repo: "${{ github.repository }}" - project_id: 16 - pull_request_number: ${{ github.event.pull_request.number }} + project_id: ${{ inputs.project_id != '' && fromJSON(inputs.project_id) || 16 }} + pull_request_number: ${{ github.event.pull_request.number || fromJSON(inputs.pr_number) }} secrets: - token: ${{ secrets.YNPUT_BOT_TOKEN }} + # INFO fallback to default `github.token` is required for PRs from forks + # INFO organization secrets won't be available to forks + token: ${{ secrets.YNPUT_BOT_TOKEN || github.token}} diff --git a/client/ayon_houdini/plugins/create/create_hda.py b/client/ayon_houdini/plugins/create/create_hda.py index 7613735f7b..0f411bb01f 100644 --- a/client/ayon_houdini/plugins/create/create_hda.py +++ b/client/ayon_houdini/plugins/create/create_hda.py @@ -141,6 +141,12 @@ def set_tool_submenu(hda_def, # endregion +def promote_spare_parameters(node): + """Promote spare parameters to HDA node type definition""" + ptg = node.parmTemplateGroup() + node.type().definition().setParmTemplateGroup(ptg) + + class CreateHDA(plugin.HoudiniCreator): """Publish Houdini Digital Asset file.""" @@ -178,12 +184,27 @@ def create_instance_node( if pre_create_data is None: pre_create_data = {} + use_promote_spare_parameters = pre_create_data.get( + "use_promote_spare_parameters", True) + if self.selected_nodes: # if we have `use selection` enabled, and we have some # selected nodes ... - if self.selected_nodes[0].type().name() == "subnet": - to_hda = self.selected_nodes[0] + one_node_selected = len(self.selected_nodes) == 1 + first_selected_node = self.selected_nodes[0] + + # If only an HDA is selected, publish just that node as HDA. + if one_node_selected and first_selected_node.type().definition(): + to_hda = first_selected_node + use_promote_spare_parameters = False + + # If only a single subnet is selected, turn that into the HDA. + elif one_node_selected and first_selected_node.type().name() == "subnet": + to_hda = first_selected_node to_hda.setName("{}_subnet".format(node_name), unique_name=True) + + # Collapse all selected nodes into a subnet and turn that into + # the HDA. else: parent_node = self.selected_nodes[0].parent() subnet = parent_node.collapseIntoSubnet( @@ -202,6 +223,7 @@ def create_instance_node( to_hda = parent_node.createNode( "subnet", node_name="{}_subnet".format(node_name)) + if not to_hda.type().definition(): # if node type has not its definition, it is not user # created hda. We test if hda can be created from the node. @@ -222,8 +244,17 @@ def create_instance_node( name=type_name, description=node_name, hda_file_name="$HIP/{}.hda".format(node_name), - ignore_external_references=True + ignore_external_references=True, + min_num_inputs=0, + max_num_inputs=len(to_hda.inputs()) or 1, ) + + if use_promote_spare_parameters: + # Similar to Houdini default behavior, when enabled this will + # promote spare parameters to type properties on initial + # creation of the HDA. + promote_spare_parameters(hda_node) + hda_node.layoutChildren() elif self._check_existing(folder_path, node_name): raise CreatorError( @@ -279,6 +310,12 @@ def get_pre_create_attr_defs(self): "'AYON/project_name/your_HDA_name'", default=True, label="Use Project as menu entry"), + BoolDef("use_promote_spare_parameters", + tooltip="Similar to Houdini default behavior, when " + "enabled this will promote spare parameters to " + "type properties on initial creation of the HDA.", + default=True, + label="Promote Spare Parameters"), ] def get_dynamic_data( diff --git a/client/ayon_houdini/plugins/load/load_hda.py b/client/ayon_houdini/plugins/load/load_hda.py index fcf0e834f8..41ad6aef42 100644 --- a/client/ayon_houdini/plugins/load/load_hda.py +++ b/client/ayon_houdini/plugins/load/load_hda.py @@ -75,6 +75,16 @@ def update(self, container, context): "representation": repre_entity["id"] }) + # Move the Extra parameter folder to the back. + parm_group = hda_node.parmTemplateGroup() + # The name 'Extra' is a hard coded name in AYON. + parm_folder = parm_group.findFolder("Extra") + # Remove `Extra` AYON parameters + parm_group.remove(parm_folder.name()) + # Add `Extra` AYON parameters back + parm_group.append(parm_folder) + hda_node.setParmTemplateGroup(parm_group) + def remove(self, container): node = container["node"] parent = node.parent() diff --git a/client/ayon_houdini/plugins/publish/extract_hda.py b/client/ayon_houdini/plugins/publish/extract_hda.py index e4449d11f8..3d49c3332f 100644 --- a/client/ayon_houdini/plugins/publish/extract_hda.py +++ b/client/ayon_houdini/plugins/publish/extract_hda.py @@ -1,11 +1,25 @@ # -*- coding: utf-8 -*- import os -from pprint import pformat +import contextlib + import hou import pyblish.api + +from ayon_core.pipeline import PublishError from ayon_houdini.api import plugin +@contextlib.contextmanager +def revert_original_parm_template_group(node: "hou.OpNode"): + """Restore parm template group after the context""" + parm_group = node.parmTemplateGroup() + try: + yield + finally: + # Set the original + node.setParmTemplateGroup(parm_group) + + class ExtractHDA(plugin.HoudiniExtractorPlugin): order = pyblish.api.ExtractorOrder @@ -13,7 +27,7 @@ class ExtractHDA(plugin.HoudiniExtractorPlugin): families = ["hda"] def process(self, instance): - self.log.info(pformat(instance.data)) + hda_node = hou.node(instance.data.get("instance_node")) hda_def = hda_node.type().definition() hda_options = hda_def.options() @@ -23,7 +37,26 @@ def process(self, instance): self.log.info("setting version: {}".format(next_version)) hda_def.setVersion(str(next_version)) hda_def.setOptions(hda_options) - hda_def.save(hda_def.libraryFilePath(), hda_node, hda_options) + + with revert_original_parm_template_group(hda_node): + # Remove our own custom parameters so that if the HDA definition + # has "Save Spare Parameters" enabled, we don't save our custom + # attributes + # Get our custom `Extra` AYON parameters + parm_group = hda_node.parmTemplateGroup() + # The name 'Extra' is a hard coded name in AYON. + parm_folder = parm_group.findFolder("Extra") + if not parm_folder: + raise PublishError( + f"Extra parm folder does not exist: {hda_node.path()}" + ) + + # Remove `Extra` AYON parameters + parm_group.remove(parm_folder.name()) + hda_node.setParmTemplateGroup(parm_group) + + # Save the HDA file + hda_def.save(hda_def.libraryFilePath(), hda_node, hda_options) if "representations" not in instance.data: instance.data["representations"] = []