mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
Merge pull request #4572 from movalex/feature/setup-fusion-profile-on-prelaunch
This commit is contained in:
commit
cbbfdb540b
8 changed files with 294 additions and 51 deletions
|
|
@ -1,10 +1,14 @@
|
|||
from .addon import (
|
||||
get_fusion_version,
|
||||
FusionAddon,
|
||||
FUSION_HOST_DIR,
|
||||
FUSION_VERSIONS_DICT,
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
"get_fusion_version",
|
||||
"FusionAddon",
|
||||
"FUSION_HOST_DIR",
|
||||
"FUSION_VERSIONS_DICT",
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,52 @@
|
|||
import os
|
||||
import re
|
||||
from openpype.modules import OpenPypeModule, IHostAddon
|
||||
from openpype.lib import Logger
|
||||
|
||||
FUSION_HOST_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
# FUSION_VERSIONS_DICT is used by the pre-launch hooks
|
||||
# The keys correspond to all currently supported Fusion versions
|
||||
# Each value is a list of corresponding Python home variables and a profile
|
||||
# number, which is used by the profile hook to set Fusion profile variables.
|
||||
FUSION_VERSIONS_DICT = {
|
||||
9: ("FUSION_PYTHON36_HOME", 9),
|
||||
16: ("FUSION16_PYTHON36_HOME", 16),
|
||||
17: ("FUSION16_PYTHON36_HOME", 16),
|
||||
18: ("FUSION_PYTHON3_HOME", 16),
|
||||
}
|
||||
|
||||
|
||||
def get_fusion_version(app_name):
|
||||
"""
|
||||
The function is triggered by the prelaunch hooks to get the fusion version.
|
||||
|
||||
`app_name` is obtained by prelaunch hooks from the
|
||||
`launch_context.env.get("AVALON_APP_NAME")`.
|
||||
|
||||
To get a correct Fusion version, a version number should be present
|
||||
in the `applications/fusion/variants` key
|
||||
of the Blackmagic Fusion Application Settings.
|
||||
"""
|
||||
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
if not app_name:
|
||||
return
|
||||
|
||||
app_version_candidates = re.findall(r"\d+", app_name)
|
||||
if not app_version_candidates:
|
||||
return
|
||||
for app_version in app_version_candidates:
|
||||
if int(app_version) in FUSION_VERSIONS_DICT:
|
||||
return int(app_version)
|
||||
else:
|
||||
log.info(
|
||||
"Unsupported Fusion version: {app_version}".format(
|
||||
app_version=app_version
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class FusionAddon(OpenPypeModule, IHostAddon):
|
||||
name = "fusion"
|
||||
|
|
@ -14,15 +58,11 @@ class FusionAddon(OpenPypeModule, IHostAddon):
|
|||
def get_launch_hook_paths(self, app):
|
||||
if app.host_name != self.host_name:
|
||||
return []
|
||||
return [
|
||||
os.path.join(FUSION_HOST_DIR, "hooks")
|
||||
]
|
||||
return [os.path.join(FUSION_HOST_DIR, "hooks")]
|
||||
|
||||
def add_implementation_envs(self, env, _app):
|
||||
# Set default values if are not already set via settings
|
||||
defaults = {
|
||||
"OPENPYPE_LOG_NO_COLORS": "Yes"
|
||||
}
|
||||
defaults = {"OPENPYPE_LOG_NO_COLORS": "Yes"}
|
||||
for key, value in defaults.items():
|
||||
if not env.get(key):
|
||||
env[key] = value
|
||||
|
|
|
|||
|
|
@ -303,10 +303,18 @@ def get_frame_path(path):
|
|||
return filename, padding, ext
|
||||
|
||||
|
||||
def get_current_comp():
|
||||
"""Hack to get current comp in this session"""
|
||||
def get_fusion_module():
|
||||
"""Get current Fusion instance"""
|
||||
fusion = getattr(sys.modules["__main__"], "fusion", None)
|
||||
return fusion.CurrentComp if fusion else None
|
||||
return fusion
|
||||
|
||||
|
||||
def get_current_comp():
|
||||
"""Get current comp in this session"""
|
||||
fusion = get_fusion_module()
|
||||
if fusion is not None:
|
||||
comp = fusion.CurrentComp
|
||||
return comp
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
{
|
||||
Locked = true,
|
||||
Global = {
|
||||
Paths = {
|
||||
Map = {
|
||||
["OpenPype:"] = "$(OPENPYPE_FUSION)/deploy",
|
||||
["Reactor:"] = "$(REACTOR)",
|
||||
|
||||
["Config:"] = "UserPaths:Config;OpenPype:Config",
|
||||
["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts;OpenPype:Scripts",
|
||||
["UserPaths:"] = "UserData:;AllData:;Fusion:;Reactor:Deploy"
|
||||
},
|
||||
},
|
||||
Script = {
|
||||
PythonVersion = 3,
|
||||
Python3Forced = true
|
||||
},
|
||||
Paths = {
|
||||
Map = {
|
||||
["OpenPype:"] = "$(OPENPYPE_FUSION)/deploy",
|
||||
["Config:"] = "UserPaths:Config;OpenPype:Config",
|
||||
["Scripts:"] = "UserPaths:Scripts;Reactor:System/Scripts;OpenPype:Scripts",
|
||||
},
|
||||
}
|
||||
},
|
||||
Script = {
|
||||
PythonVersion = 3,
|
||||
Python3Forced = true
|
||||
},
|
||||
UserInterface = {
|
||||
Language = "en_US"
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
|
|||
161
openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py
Normal file
161
openpype/hosts/fusion/hooks/pre_fusion_profile_hook.py
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
import os
|
||||
import shutil
|
||||
import platform
|
||||
from pathlib import Path
|
||||
from openpype.lib import PreLaunchHook, ApplicationLaunchFailed
|
||||
from openpype.hosts.fusion import (
|
||||
FUSION_HOST_DIR,
|
||||
FUSION_VERSIONS_DICT,
|
||||
get_fusion_version,
|
||||
)
|
||||
|
||||
|
||||
class FusionCopyPrefsPrelaunch(PreLaunchHook):
|
||||
"""
|
||||
Prepares local Fusion profile directory, copies existing Fusion profile.
|
||||
This also sets FUSION MasterPrefs variable, which is used
|
||||
to apply Master.prefs file to override some Fusion profile settings to:
|
||||
- enable the OpenPype menu
|
||||
- force Python 3 over Python 2
|
||||
- force English interface
|
||||
Master.prefs is defined in openpype/hosts/fusion/deploy/fusion_shared.prefs
|
||||
"""
|
||||
|
||||
app_groups = ["fusion"]
|
||||
order = 2
|
||||
|
||||
def get_fusion_profile_name(self, profile_version) -> str:
|
||||
# Returns 'Default', unless FUSION16_PROFILE is set
|
||||
return os.getenv(f"FUSION{profile_version}_PROFILE", "Default")
|
||||
|
||||
def get_fusion_profile_dir(self, profile_version) -> Path:
|
||||
# Get FUSION_PROFILE_DIR variable
|
||||
fusion_profile = self.get_fusion_profile_name(profile_version)
|
||||
fusion_var_prefs_dir = os.getenv(
|
||||
f"FUSION{profile_version}_PROFILE_DIR"
|
||||
)
|
||||
|
||||
# Check if FUSION_PROFILE_DIR exists
|
||||
if fusion_var_prefs_dir and Path(fusion_var_prefs_dir).is_dir():
|
||||
fu_prefs_dir = Path(fusion_var_prefs_dir, fusion_profile)
|
||||
self.log.info(f"{fusion_var_prefs_dir} is set to {fu_prefs_dir}")
|
||||
return fu_prefs_dir
|
||||
|
||||
def get_profile_source(self, profile_version) -> Path:
|
||||
"""Get Fusion preferences profile location.
|
||||
See Per-User_Preferences_and_Paths on VFXpedia for reference.
|
||||
"""
|
||||
fusion_profile = self.get_fusion_profile_name(profile_version)
|
||||
profile_source = self.get_fusion_profile_dir(profile_version)
|
||||
if profile_source:
|
||||
return profile_source
|
||||
# otherwise get default location of the profile folder
|
||||
fu_prefs_dir = f"Blackmagic Design/Fusion/Profiles/{fusion_profile}"
|
||||
if platform.system() == "Windows":
|
||||
profile_source = Path(os.getenv("AppData"), fu_prefs_dir)
|
||||
elif platform.system() == "Darwin":
|
||||
profile_source = Path(
|
||||
"~/Library/Application Support/", fu_prefs_dir
|
||||
).expanduser()
|
||||
elif platform.system() == "Linux":
|
||||
profile_source = Path("~/.fusion", fu_prefs_dir).expanduser()
|
||||
self.log.info(
|
||||
f"Locating source Fusion prefs directory: {profile_source}"
|
||||
)
|
||||
return profile_source
|
||||
|
||||
def get_copy_fusion_prefs_settings(self):
|
||||
# Get copy preferences options from the global application settings
|
||||
|
||||
copy_fusion_settings = self.data["project_settings"]["fusion"].get(
|
||||
"copy_fusion_settings", {}
|
||||
)
|
||||
if not copy_fusion_settings:
|
||||
self.log.error("Copy prefs settings not found")
|
||||
copy_status = copy_fusion_settings.get("copy_status", False)
|
||||
force_sync = copy_fusion_settings.get("force_sync", False)
|
||||
copy_path = copy_fusion_settings.get("copy_path") or None
|
||||
if copy_path:
|
||||
copy_path = Path(copy_path).expanduser()
|
||||
return copy_status, copy_path, force_sync
|
||||
|
||||
def copy_fusion_profile(
|
||||
self, copy_from: Path, copy_to: Path, force_sync: bool
|
||||
) -> None:
|
||||
"""On the first Fusion launch copy the contents of Fusion profile
|
||||
directory to the working predefined location. If the Openpype profile
|
||||
folder exists, skip copying, unless re-sync is checked.
|
||||
If the prefs were not copied on the first launch,
|
||||
clean Fusion profile will be created in fu_profile_dir.
|
||||
"""
|
||||
if copy_to.exists() and not force_sync:
|
||||
self.log.info(
|
||||
"Destination Fusion preferences folder already exists: "
|
||||
f"{copy_to} "
|
||||
)
|
||||
return
|
||||
self.log.info("Starting copying Fusion preferences")
|
||||
self.log.debug(f"force_sync option is set to {force_sync}")
|
||||
try:
|
||||
copy_to.mkdir(exist_ok=True, parents=True)
|
||||
except PermissionError:
|
||||
self.log.warning(f"Creating the folder not permitted at {copy_to}")
|
||||
return
|
||||
if not copy_from.exists():
|
||||
self.log.warning(f"Fusion preferences not found in {copy_from}")
|
||||
return
|
||||
for file in copy_from.iterdir():
|
||||
if file.suffix in (
|
||||
".prefs",
|
||||
".def",
|
||||
".blocklist",
|
||||
".fu",
|
||||
".toolbars",
|
||||
):
|
||||
# convert Path to str to be compatible with Python 3.6+
|
||||
shutil.copy(str(file), str(copy_to))
|
||||
self.log.info(
|
||||
f"Successfully copied preferences: {copy_from} to {copy_to}"
|
||||
)
|
||||
|
||||
def execute(self):
|
||||
(
|
||||
copy_status,
|
||||
fu_profile_dir,
|
||||
force_sync,
|
||||
) = self.get_copy_fusion_prefs_settings()
|
||||
|
||||
# Get launched application context and return correct app version
|
||||
app_name = self.launch_context.env.get("AVALON_APP_NAME")
|
||||
app_version = get_fusion_version(app_name)
|
||||
if app_version is None:
|
||||
version_names = ", ".join(str(x) for x in FUSION_VERSIONS_DICT)
|
||||
raise ApplicationLaunchFailed(
|
||||
"Unable to detect valid Fusion version number from app "
|
||||
f"name: {app_name}.\nMake sure to include at least a digit "
|
||||
"to indicate the Fusion version like '18'.\n"
|
||||
f"Detectable Fusion versions are: {version_names}"
|
||||
)
|
||||
|
||||
_, profile_version = FUSION_VERSIONS_DICT[app_version]
|
||||
fu_profile = self.get_fusion_profile_name(profile_version)
|
||||
|
||||
# do a copy of Fusion profile if copy_status toggle is enabled
|
||||
if copy_status and fu_profile_dir is not None:
|
||||
profile_source = self.get_profile_source(profile_version)
|
||||
dest_folder = Path(fu_profile_dir, fu_profile)
|
||||
self.copy_fusion_profile(profile_source, dest_folder, force_sync)
|
||||
|
||||
# Add temporary profile directory variables to customize Fusion
|
||||
# to define where it can read custom scripts and tools from
|
||||
fu_profile_dir_variable = f"FUSION{profile_version}_PROFILE_DIR"
|
||||
self.log.info(f"Setting {fu_profile_dir_variable}: {fu_profile_dir}")
|
||||
self.launch_context.env[fu_profile_dir_variable] = str(fu_profile_dir)
|
||||
|
||||
# Add custom Fusion Master Prefs and the temporary
|
||||
# profile directory variables to customize Fusion
|
||||
# to define where it can read custom scripts and tools from
|
||||
master_prefs_variable = f"FUSION{profile_version}_MasterPrefs"
|
||||
master_prefs = Path(FUSION_HOST_DIR, "deploy", "fusion_shared.prefs")
|
||||
self.log.info(f"Setting {master_prefs_variable}: {master_prefs}")
|
||||
self.launch_context.env[master_prefs_variable] = str(master_prefs)
|
||||
|
|
@ -1,32 +1,43 @@
|
|||
import os
|
||||
from openpype.lib import PreLaunchHook, ApplicationLaunchFailed
|
||||
from openpype.hosts.fusion import FUSION_HOST_DIR
|
||||
from openpype.hosts.fusion import (
|
||||
FUSION_HOST_DIR,
|
||||
FUSION_VERSIONS_DICT,
|
||||
get_fusion_version,
|
||||
)
|
||||
|
||||
|
||||
class FusionPrelaunch(PreLaunchHook):
|
||||
"""Prepares OpenPype Fusion environment
|
||||
|
||||
Requires FUSION_PYTHON3_HOME to be defined in the environment for Fusion
|
||||
to point at a valid Python 3 build for Fusion. That is Python 3.3-3.10
|
||||
for Fusion 18 and Fusion 3.6 for Fusion 16 and 17.
|
||||
|
||||
This also sets FUSION16_MasterPrefs to apply the fusion master prefs
|
||||
as set in openpype/hosts/fusion/deploy/fusion_shared.prefs to enable
|
||||
the OpenPype menu and force Python 3 over Python 2.
|
||||
|
||||
"""
|
||||
Prepares OpenPype Fusion environment.
|
||||
Requires correct Python home variable to be defined in the environment
|
||||
settings for Fusion to point at a valid Python 3 build for Fusion.
|
||||
Python3 versions that are supported by Fusion:
|
||||
Fusion 9, 16, 17 : Python 3.6
|
||||
Fusion 18 : Python 3.6 - 3.10
|
||||
"""
|
||||
|
||||
app_groups = ["fusion"]
|
||||
order = 1
|
||||
|
||||
def execute(self):
|
||||
# making sure python 3 is installed at provided path
|
||||
# Py 3.3-3.10 for Fusion 18+ or Py 3.6 for Fu 16-17
|
||||
py3_var = "FUSION_PYTHON3_HOME"
|
||||
app_data = self.launch_context.env.get("AVALON_APP_NAME")
|
||||
app_version = get_fusion_version(app_data)
|
||||
if not app_version:
|
||||
raise ApplicationLaunchFailed(
|
||||
"Fusion version information not found in System settings.\n"
|
||||
"The key field in the 'applications/fusion/variants' should "
|
||||
"consist a number, corresponding to major Fusion version."
|
||||
)
|
||||
py3_var, _ = FUSION_VERSIONS_DICT[app_version]
|
||||
fusion_python3_home = self.launch_context.env.get(py3_var, "")
|
||||
|
||||
self.log.info(f"Looking for Python 3 in: {fusion_python3_home}")
|
||||
for path in fusion_python3_home.split(os.pathsep):
|
||||
# Allow defining multiple paths to allow "fallback" to other
|
||||
# path. But make to set only a single path as final variable.
|
||||
# Allow defining multiple paths, separated by os.pathsep,
|
||||
# to allow "fallback" to other path.
|
||||
# But make to set only a single path as final variable.
|
||||
py3_dir = os.path.normpath(path)
|
||||
if os.path.isdir(py3_dir):
|
||||
break
|
||||
|
|
@ -43,19 +54,10 @@ class FusionPrelaunch(PreLaunchHook):
|
|||
self.launch_context.env[py3_var] = py3_dir
|
||||
|
||||
# Fusion 18+ requires FUSION_PYTHON3_HOME to also be on PATH
|
||||
self.launch_context.env["PATH"] += ";" + py3_dir
|
||||
if app_version >= 18:
|
||||
self.launch_context.env["PATH"] += os.pathsep + py3_dir
|
||||
|
||||
# Fusion 16 and 17 use FUSION16_PYTHON36_HOME instead of
|
||||
# FUSION_PYTHON3_HOME and will only work with a Python 3.6 version
|
||||
# TODO: Detect Fusion version to only set for specific Fusion build
|
||||
self.launch_context.env["FUSION16_PYTHON36_HOME"] = py3_dir
|
||||
self.launch_context.env[py3_var] = py3_dir
|
||||
|
||||
# Add our Fusion Master Prefs which is the only way to customize
|
||||
# Fusion to define where it can read custom scripts and tools from
|
||||
self.log.info(f"Setting OPENPYPE_FUSION: {FUSION_HOST_DIR}")
|
||||
self.launch_context.env["OPENPYPE_FUSION"] = FUSION_HOST_DIR
|
||||
|
||||
pref_var = "FUSION16_MasterPrefs" # used by Fusion 16, 17 and 18
|
||||
prefs = os.path.join(FUSION_HOST_DIR, "deploy", "fusion_shared.prefs")
|
||||
self.log.info(f"Setting {pref_var}: {prefs}")
|
||||
self.launch_context.env[pref_var] = prefs
|
||||
|
|
|
|||
|
|
@ -16,5 +16,10 @@
|
|||
"linux": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"copy_fusion_settings": {
|
||||
"copy_path": "~/.openpype/hosts/fusion/profiles",
|
||||
"copy_status": false,
|
||||
"force_sync": false
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,6 +45,29 @@
|
|||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"key": "copy_fusion_settings",
|
||||
"collapsible": true,
|
||||
"label": "Local Fusion profile settings",
|
||||
"children": [
|
||||
{
|
||||
"key": "copy_path",
|
||||
"type": "path",
|
||||
"label": "Local Fusion profile directory"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "copy_status",
|
||||
"label": "Copy profile on first launch"
|
||||
},
|
||||
{
|
||||
"key":"force_sync",
|
||||
"type": "boolean",
|
||||
"label": "Resync profile on each launch"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue