Merge pull request #5651 from ynput/bugfix/OP-4326_Houdini-switching-context-doesnt-update-variables

This commit is contained in:
Milan Kolar 2023-10-06 18:41:48 +02:00 committed by GitHub
commit d41ac2a3fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 342 additions and 8 deletions

View file

@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
import sys
import os
import errno
import re
import uuid
import logging
@ -9,10 +10,15 @@ import json
import six
from openpype.lib import StringTemplate
from openpype.client import get_asset_by_name
from openpype.settings import get_current_project_settings
from openpype.pipeline import get_current_project_name, get_current_asset_name
from openpype.pipeline.context_tools import get_current_project_asset
from openpype.pipeline.context_tools import (
get_current_context_template_data,
get_current_project_asset
)
from openpype.widgets import popup
import hou
@ -160,8 +166,6 @@ def validate_fps():
if current_fps != fps:
from openpype.widgets import popup
# Find main window
parent = hou.ui.mainQtWindow()
if parent is None:
@ -747,3 +751,99 @@ def get_camera_from_container(container):
assert len(cameras) == 1, "Camera instance must have only one camera"
return cameras[0]
def get_context_var_changes():
"""get context var changes."""
houdini_vars_to_update = {}
project_settings = get_current_project_settings()
houdini_vars_settings = \
project_settings["houdini"]["general"]["update_houdini_var_context"]
if not houdini_vars_settings["enabled"]:
return houdini_vars_to_update
houdini_vars = houdini_vars_settings["houdini_vars"]
# No vars specified - nothing to do
if not houdini_vars:
return houdini_vars_to_update
# Get Template data
template_data = get_current_context_template_data()
# Set Houdini Vars
for item in houdini_vars:
# For consistency reasons we always force all vars to be uppercase
# Also remove any leading, and trailing whitespaces.
var = item["var"].strip().upper()
# get and resolve template in value
item_value = StringTemplate.format_template(
item["value"],
template_data
)
if var == "JOB" and item_value == "":
# sync $JOB to $HIP if $JOB is empty
item_value = os.environ["HIP"]
if item["is_directory"]:
item_value = item_value.replace("\\", "/")
current_value = hou.hscript("echo -n `${}`".format(var))[0]
if current_value != item_value:
houdini_vars_to_update[var] = (
current_value, item_value, item["is_directory"]
)
return houdini_vars_to_update
def update_houdini_vars_context():
"""Update asset context variables"""
for var, (_old, new, is_directory) in get_context_var_changes().items():
if is_directory:
try:
os.makedirs(new)
except OSError as e:
if e.errno != errno.EEXIST:
print(
"Failed to create ${} dir. Maybe due to "
"insufficient permissions.".format(var)
)
hou.hscript("set {}={}".format(var, new))
os.environ[var] = new
print("Updated ${} to {}".format(var, new))
def update_houdini_vars_context_dialog():
"""Show pop-up to update asset context variables"""
update_vars = get_context_var_changes()
if not update_vars:
# Nothing to change
print("Nothing to change, Houdini vars are already up to date.")
return
message = "\n".join(
"${}: {} -> {}".format(var, old or "None", new or "None")
for var, (old, new, _is_directory) in update_vars.items()
)
# TODO: Use better UI!
parent = hou.ui.mainQtWindow()
dialog = popup.Popup(parent=parent)
dialog.setModal(True)
dialog.setWindowTitle("Houdini scene has outdated asset variables")
dialog.setMessage(message)
dialog.setButtonText("Fix")
# on_show is the Fix button clicked callback
dialog.on_clicked.connect(update_houdini_vars_context)
dialog.show()

View file

@ -300,6 +300,9 @@ def on_save():
log.info("Running callback on save..")
# update houdini vars
lib.update_houdini_vars_context_dialog()
nodes = lib.get_id_required_nodes()
for node, new_id in lib.generate_ids(nodes):
lib.set_id(node, new_id, overwrite=False)
@ -335,6 +338,9 @@ def on_open():
log.info("Running callback on open..")
# update houdini vars
lib.update_houdini_vars_context_dialog()
# Validate FPS after update_task_from_path to
# ensure it is using correct FPS for the asset
lib.validate_fps()
@ -399,6 +405,7 @@ def _set_context_settings():
"""
lib.reset_framerange()
lib.update_houdini_vars_context()
def on_pyblish_instance_toggled(instance, new_value, old_value):

View file

@ -86,6 +86,14 @@ openpype.hosts.houdini.api.lib.reset_framerange()
]]></scriptCode>
</scriptItem>
<scriptItem id="update_context_vars">
<label>Update Houdini Vars</label>
<scriptCode><![CDATA[
import openpype.hosts.houdini.api.lib
openpype.hosts.houdini.api.lib.update_houdini_vars_context_dialog()
]]></scriptCode>
</scriptItem>
<separatorItem/>
<scriptItem id="experimental_tools">
<label>Experimental tools...</label>

View file

@ -25,7 +25,10 @@ from openpype.tests.lib import is_in_tests
from .publish.lib import filter_pyblish_plugins
from .anatomy import Anatomy
from .template_data import get_template_data_with_names
from .template_data import (
get_template_data_with_names,
get_template_data
)
from .workfile import (
get_workfile_template_key,
get_custom_workfile_template_by_string_context,
@ -658,3 +661,70 @@ def get_process_id():
if _process_id is None:
_process_id = str(uuid.uuid4())
return _process_id
def get_current_context_template_data():
"""Template data for template fill from current context
Returns:
Dict[str, Any] of the following tokens and their values
Supported Tokens:
- Regular Tokens
- app
- user
- asset
- parent
- hierarchy
- folder[name]
- root[work, ...]
- studio[code, name]
- project[code, name]
- task[type, name, short]
- Context Specific Tokens
- assetData[frameStart]
- assetData[frameEnd]
- assetData[handleStart]
- assetData[handleEnd]
- assetData[frameStartHandle]
- assetData[frameEndHandle]
- assetData[resolutionHeight]
- assetData[resolutionWidth]
"""
# pre-prepare get_template_data args
current_context = get_current_context()
project_name = current_context["project_name"]
asset_name = current_context["asset_name"]
anatomy = Anatomy(project_name)
# prepare get_template_data args
project_doc = get_project(project_name)
asset_doc = get_asset_by_name(project_name, asset_name)
task_name = current_context["task_name"]
host_name = get_current_host_name()
# get regular template data
template_data = get_template_data(
project_doc, asset_doc, task_name, host_name
)
template_data["root"] = anatomy.roots
# get context specific vars
asset_data = asset_doc["data"].copy()
# compute `frameStartHandle` and `frameEndHandle`
if "frameStart" in asset_data and "handleStart" in asset_data:
asset_data["frameStartHandle"] = \
asset_data["frameStart"] - asset_data["handleStart"]
if "frameEnd" in asset_data and "handleEnd" in asset_data:
asset_data["frameEndHandle"] = \
asset_data["frameEnd"] + asset_data["handleEnd"]
# add assetData
template_data["assetData"] = asset_data
return template_data

View file

@ -1,4 +1,16 @@
{
"general": {
"update_houdini_var_context": {
"enabled": true,
"houdini_vars":[
{
"var": "JOB",
"value": "{root[work]}/{project[name]}/{hierarchy}/{asset}/work/{task[name]}",
"is_directory": true
}
]
}
},
"imageio": {
"activate_host_color_management": true,
"ocio_config": {

View file

@ -5,6 +5,10 @@
"label": "Houdini",
"is_file": true,
"children": [
{
"type": "schema",
"name": "schema_houdini_general"
},
{
"key": "imageio",
"type": "dict",

View file

@ -0,0 +1,53 @@
{
"type": "dict",
"key": "general",
"label": "General",
"collapsible": true,
"is_group": true,
"children": [
{
"type": "dict",
"collapsible": true,
"checkbox_key": "enabled",
"key": "update_houdini_var_context",
"label": "Update Houdini Vars on context change",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "label",
"label": "Sync vars with context changes.<br>If a value is treated as a directory on update it will be ensured the folder exists"
},
{
"type": "list",
"key": "houdini_vars",
"label": "Houdini Vars",
"collapsible": false,
"object_type": {
"type": "dict",
"children": [
{
"type": "text",
"key": "var",
"label": "Var"
},
{
"type": "text",
"key": "value",
"label": "Value"
},
{
"type": "boolean",
"key": "is_directory",
"label": "Treat as directory"
}
]
}
}
]
}
]
}

View file

@ -0,0 +1,45 @@
from pydantic import Field
from ayon_server.settings import BaseSettingsModel
class HoudiniVarModel(BaseSettingsModel):
_layout = "expanded"
var: str = Field("", title="Var")
value: str = Field("", title="Value")
is_directory: bool = Field(False, title="Treat as directory")
class UpdateHoudiniVarcontextModel(BaseSettingsModel):
"""Sync vars with context changes.
If a value is treated as a directory on update
it will be ensured the folder exists.
"""
enabled: bool = Field(title="Enabled")
# TODO this was dynamic dictionary '{var: path}'
houdini_vars: list[HoudiniVarModel] = Field(
default_factory=list,
title="Houdini Vars"
)
class GeneralSettingsModel(BaseSettingsModel):
update_houdini_var_context: UpdateHoudiniVarcontextModel = Field(
default_factory=UpdateHoudiniVarcontextModel,
title="Update Houdini Vars on context change"
)
DEFAULT_GENERAL_SETTINGS = {
"update_houdini_var_context": {
"enabled": True,
"houdini_vars": [
{
"var": "JOB",
"value": "{root[work]}/{project[name]}/{hierarchy}/{asset}/work/{task[name]}", # noqa
"is_directory": True
}
]
}
}

View file

@ -4,7 +4,10 @@ from ayon_server.settings import (
MultiplatformPathModel,
MultiplatformPathListModel,
)
from .general import (
GeneralSettingsModel,
DEFAULT_GENERAL_SETTINGS
)
from .imageio import HoudiniImageIOModel
from .publish_plugins import (
PublishPluginsModel,
@ -52,6 +55,10 @@ class ShelvesModel(BaseSettingsModel):
class HoudiniSettings(BaseSettingsModel):
general: GeneralSettingsModel = Field(
default_factory=GeneralSettingsModel,
title="General"
)
imageio: HoudiniImageIOModel = Field(
default_factory=HoudiniImageIOModel,
title="Color Management (ImageIO)"
@ -73,6 +80,7 @@ class HoudiniSettings(BaseSettingsModel):
DEFAULT_VALUES = {
"general": DEFAULT_GENERAL_SETTINGS,
"shelves": [],
"create": DEFAULT_HOUDINI_CREATE_SETTINGS,
"publish": DEFAULT_HOUDINI_PUBLISH_SETTINGS

View file

@ -1 +1 @@
__version__ = "0.1.3"
__version__ = "0.1.4"

View file

@ -3,9 +3,36 @@ id: admin_hosts_houdini
title: Houdini
sidebar_label: Houdini
---
## General Settings
### Houdini Vars
Allows admins to have a list of vars (e.g. JOB) with (dynamic) values that will be updated on context changes, e.g. when switching to another asset or task.
Using template keys is supported but formatting keys capitalization variants is not, e.g. `{Asset}` and `{ASSET}` won't work
:::note
If `Treat as directory` toggle is activated, Openpype will consider the given value is a path of a folder.
If the folder does not exist on the context change it will be created by this feature so that the path will always try to point to an existing folder.
:::
Disabling `Update Houdini vars on context change` feature will leave all Houdini vars unmanaged and thus no context update changes will occur.
> If `$JOB` is present in the Houdini var list and has an empty value, OpenPype will set its value to `$HIP`
:::note
For consistency reasons we always force all vars to be uppercase.
e.g. `myvar` will be `MYVAR`
:::
![update-houdini-vars-context-change](assets/houdini/update-houdini-vars-context-change.png)
## Shelves Manager
You can add your custom shelf set into Houdini by setting your shelf sets, shelves and tools in **Houdini -> Shelves Manager**.
![Custom menu definition](assets/houdini-admin_shelvesmanager.png)
The Shelf Set Path is used to load a .shelf file to generate your shelf set. If the path is specified, you don't have to set the shelves and tools.
The Shelf Set Path is used to load a .shelf file to generate your shelf set. If the path is specified, you don't have to set the shelves and tools.

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB