add some comment

This commit is contained in:
Kayla Man 2023-06-02 17:18:13 +08:00
parent 22251f4958
commit 7075d5c445
21 changed files with 573 additions and 45 deletions

View file

@ -0,0 +1,55 @@
from pathlib import Path
from openpype.lib import PreLaunchHook
class AddPythonScriptToLaunchArgs(PreLaunchHook):
"""Add python script to be executed before Blender launch."""
# Append after file argument
order = 15
app_groups = [
"blender",
]
def execute(self):
if not self.launch_context.data.get("python_scripts"):
return
# Add path to workfile to arguments
for python_script_path in self.launch_context.data["python_scripts"]:
self.log.info(
f"Adding python script {python_script_path} to launch"
)
# Test script path exists
python_script_path = Path(python_script_path)
if not python_script_path.exists():
self.log.warning(
f"Python script {python_script_path} doesn't exist. "
"Skipped..."
)
continue
if "--" in self.launch_context.launch_args:
# Insert before separator
separator_index = self.launch_context.launch_args.index("--")
self.launch_context.launch_args.insert(
separator_index,
"-P",
)
self.launch_context.launch_args.insert(
separator_index + 1,
python_script_path.as_posix(),
)
else:
self.launch_context.launch_args.extend(
["-P", python_script_path.as_posix()]
)
# Ensure separator
if "--" not in self.launch_context.launch_args:
self.launch_context.launch_args.append("--")
self.launch_context.launch_args.extend(
[*self.launch_context.data.get("script_args", [])]
)

View file

@ -128,7 +128,14 @@ def get_all_children(parent, node_type=None):
def get_current_renderer(): def get_current_renderer():
"""get current renderer""" """
Notes:
Get current renderer for Max
Returns:
"{Current Renderer}:{Current Renderer}"
e.g. "Redshift_Renderer:Redshift_Renderer"
"""
return rt.renderers.production return rt.renderers.production

View file

@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating camera."""
from openpype.hosts.max.api import plugin
from openpype.pipeline import CreatedInstance
class CreateRedshiftProxy(plugin.MaxCreator):
identifier = "io.openpype.creators.max.redshiftproxy"
label = "Redshift Proxy"
family = "redshiftproxy"
icon = "gear"
def create(self, subset_name, instance_data, pre_create_data):
_ = super(CreateRedshiftProxy, self).create(
subset_name,
instance_data,
pre_create_data) # type: CreatedInstance

View file

@ -0,0 +1,63 @@
import os
import clique
from openpype.pipeline import (
load,
get_representation_path
)
from openpype.hosts.max.api.pipeline import containerise
from openpype.hosts.max.api import lib
class RedshiftProxyLoader(load.LoaderPlugin):
"""Load rs files with Redshift Proxy"""
label = "Load Redshift Proxy"
families = ["redshiftproxy"]
representations = ["rs"]
order = -9
icon = "code-fork"
color = "white"
def load(self, context, name=None, namespace=None, data=None):
from pymxs import runtime as rt
filepath = self.filepath_from_context(context)
rs_proxy = rt.RedshiftProxy()
rs_proxy.file = filepath
files_in_folder = os.listdir(os.path.dirname(filepath))
collections, remainder = clique.assemble(files_in_folder)
if collections:
rs_proxy.is_sequence = True
container = rt.container()
container.name = name
rs_proxy.Parent = container
asset = rt.getNodeByName(name)
return containerise(
name, [asset], context, loader=self.__class__.__name__)
def update(self, container, representation):
from pymxs import runtime as rt
path = get_representation_path(representation)
node = rt.getNodeByName(container["instance_node"])
for children in node.Children:
children_node = rt.getNodeByName(children.name)
for proxy in children_node.Children:
proxy.file = path
lib.imprint(container["instance_node"], {
"representation": str(representation["_id"])
})
def switch(self, container, representation):
self.update(container, representation)
def remove(self, container):
from pymxs import runtime as rt
node = rt.getNodeByName(container["instance_node"])
rt.delete(node)

View file

@ -0,0 +1,62 @@
import os
import pyblish.api
from openpype.pipeline import publish
from pymxs import runtime as rt
from openpype.hosts.max.api import maintained_selection
class ExtractRedshiftProxy(publish.Extractor):
"""
Extract Redshift Proxy with rsProxy
"""
order = pyblish.api.ExtractorOrder - 0.1
label = "Extract RedShift Proxy"
hosts = ["max"]
families = ["redshiftproxy"]
def process(self, instance):
container = instance.data["instance_node"]
start = int(instance.context.data.get("frameStart"))
end = int(instance.context.data.get("frameEnd"))
self.log.info("Extracting Redshift Proxy...")
stagingdir = self.staging_dir(instance)
rs_filename = "{name}.rs".format(**instance.data)
rs_filepath = os.path.join(stagingdir, rs_filename)
rs_filepath = rs_filepath.replace("\\", "/")
rs_filenames = self.get_rsfiles(instance, start, end)
with maintained_selection():
# select and export
con = rt.getNodeByName(container)
rt.select(con.Children)
# Redshift rsProxy command
# rsProxy fp selected compress connectivity startFrame endFrame
# camera warnExisting transformPivotToOrigin
rt.rsProxy(rs_filepath, 1, 0, 0, start, end, 0, 1, 1)
self.log.info("Performing Extraction ...")
if "representations" not in instance.data:
instance.data["representations"] = []
representation = {
'name': 'rs',
'ext': 'rs',
'files': rs_filenames if len(rs_filenames) > 1 else rs_filenames[0], # noqa
"stagingDir": stagingdir,
}
instance.data["representations"].append(representation)
self.log.info("Extracted instance '%s' to: %s" % (instance.name,
stagingdir))
def get_rsfiles(self, instance, startFrame, endFrame):
rs_filenames = []
rs_name = instance.data["name"]
for frame in range(startFrame, endFrame + 1):
rs_filename = "%s.%04d.rs" % (rs_name, frame)
rs_filenames.append(rs_filename)
return rs_filenames

View file

@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
import pyblish.api
from openpype.pipeline import PublishValidationError
from pymxs import runtime as rt
from openpype.pipeline.publish import RepairAction
from openpype.hosts.max.api.lib import get_current_renderer
class ValidateRendererRedshiftProxy(pyblish.api.InstancePlugin):
"""
Validates Redshift as the current renderer for creating
Redshift Proxy
"""
order = pyblish.api.ValidatorOrder
families = ["redshiftproxy"]
hosts = ["max"]
label = "Redshift Renderer"
actions = [RepairAction]
def process(self, instance):
invalid = self.get_redshift_renderer(instance)
if invalid:
raise PublishValidationError("Please install Redshift for 3dsMax"
" before using the Redshift proxy instance") # noqa
invalid = self.get_current_renderer(instance)
if invalid:
raise PublishValidationError("The Redshift proxy extraction"
"discontinued since the current renderer is not Redshift") # noqa
def get_redshift_renderer(self, instance):
invalid = list()
max_renderers_list = str(rt.RendererClass.classes)
if "Redshift_Renderer" not in max_renderers_list:
invalid.append(max_renderers_list)
return invalid
def get_current_renderer(self, instance):
invalid = list()
renderer_class = get_current_renderer()
current_renderer = str(renderer_class).split(":")[0]
if current_renderer != "Redshift_Renderer":
invalid.append(current_renderer)
return invalid
@classmethod
def repair(cls, instance):
for Renderer in rt.RendererClass.classes:
renderer = Renderer()
if "Redshift_Renderer" in str(renderer):
rt.renderers.production = renderer
break

View file

View file

@ -68,6 +68,7 @@ def main():
if knob["name"] == "file_type": if knob["name"] == "file_type":
ext = knob["value"] ext = knob["value"]
for w in write_selected_nodes: for w in write_selected_nodes:
# data for mapping the path
data = { data = {
"work": os.getenv("AVALON_WORKDIR"), "work": os.getenv("AVALON_WORKDIR"),
"subset": w["name"].value(), "subset": w["name"].value(),

View file

@ -0,0 +1,47 @@
""" OpenPype custom script for resetting read nodes start frame values """
import nuke
import nukescripts
class FrameSettingsPanel(nukescripts.PythonPanel):
""" Frame Settings Panel """
def __init__(self):
nukescripts.PythonPanel.__init__(self, "Set Frame Start (Read Node)")
# create knobs
self.frame = nuke.Int_Knob(
'frame', 'Frame Number')
self.selected = nuke.Boolean_Knob("selection")
# add knobs to panel
self.addKnob(self.selected)
self.addKnob(self.frame)
# set values
self.selected.setValue(False)
self.frame.setValue(nuke.root().firstFrame())
def process(self):
""" Process the panel values. """
# get values
frame = self.frame.value()
if self.selected.value():
# selected nodes processing
if not nuke.selectedNodes():
return
for rn_ in nuke.selectedNodes():
if rn_.Class() != "Read":
continue
rn_["frame_mode"].setValue("start_at")
rn_["frame"].setValue(str(frame))
else:
# all nodes processing
for rn_ in nuke.allNodes(filter="Read"):
rn_["frame_mode"].setValue("start_at")
rn_["frame"].setValue(str(frame))
def main():
p_ = FrameSettingsPanel()
if p_.showModalDialog():
print(p_.process())

View file

@ -43,18 +43,22 @@ def open_file(filepath):
""" """
Loading project Loading project
""" """
from . import bmdvr
pm = get_project_manager() pm = get_project_manager()
page = bmdvr.GetCurrentPage()
if page is not None:
# Save current project only if Resolve has an active page, otherwise
# we consider Resolve being in a pre-launch state (no open UI yet)
project = pm.GetCurrentProject()
print(f"Saving current project: {project}")
pm.SaveProject()
file = os.path.basename(filepath) file = os.path.basename(filepath)
fname, _ = os.path.splitext(file) fname, _ = os.path.splitext(file)
dname, _ = fname.split("_v") dname, _ = fname.split("_v")
# deal with current project
project = pm.GetCurrentProject()
log.info(f"Test `pm`: {pm}")
pm.SaveProject()
try: try:
log.info(f"Test `dname`: {dname}")
if not set_project_manager_to_folder_name(dname): if not set_project_manager_to_folder_name(dname):
raise raise
# load project from input path # load project from input path
@ -72,6 +76,7 @@ def open_file(filepath):
return False return False
return True return True
def current_file(): def current_file():
pm = get_project_manager() pm = get_project_manager()
current_dir = os.getenv("AVALON_WORKDIR") current_dir = os.getenv("AVALON_WORKDIR")

View file

@ -0,0 +1,45 @@
import os
from openpype.lib import PreLaunchHook
import openpype.hosts.resolve
class ResolveLaunchLastWorkfile(PreLaunchHook):
"""Special hook to open last workfile for Resolve.
Checks 'start_last_workfile', if set to False, it will not open last
workfile. This property is set explicitly in Launcher.
"""
# Execute after workfile template copy
order = 10
app_groups = ["resolve"]
def execute(self):
if not self.data.get("start_last_workfile"):
self.log.info("It is set to not start last workfile on start.")
return
last_workfile = self.data.get("last_workfile_path")
if not last_workfile:
self.log.warning("Last workfile was not collected.")
return
if not os.path.exists(last_workfile):
self.log.info("Current context does not have any workfile yet.")
return
# Add path to launch environment for the startup script to pick up
self.log.info(f"Setting OPENPYPE_RESOLVE_OPEN_ON_LAUNCH to launch "
f"last workfile: {last_workfile}")
key = "OPENPYPE_RESOLVE_OPEN_ON_LAUNCH"
self.launch_context.env[key] = last_workfile
# Set the openpype prelaunch startup script path for easy access
# in the LUA .scriptlib code
op_resolve_root = os.path.dirname(openpype.hosts.resolve.__file__)
script_path = os.path.join(op_resolve_root, "startup.py")
key = "OPENPYPE_RESOLVE_STARTUP_SCRIPT"
self.launch_context.env[key] = script_path
self.log.info("Setting OPENPYPE_RESOLVE_STARTUP_SCRIPT to: "
f"{script_path}")

View file

@ -0,0 +1,62 @@
"""This script is used as a startup script in Resolve through a .scriptlib file
It triggers directly after the launch of Resolve and it's recommended to keep
it optimized for fast performance since the Resolve UI is actually interactive
while this is running. As such, there's nothing ensuring the user isn't
continuing manually before any of the logic here runs. As such we also try
to delay any imports as much as possible.
This code runs in a separate process to the main Resolve process.
"""
import os
import openpype.hosts.resolve.api
def ensure_installed_host():
"""Install resolve host with openpype and return the registered host.
This function can be called multiple times without triggering an
additional install.
"""
from openpype.pipeline import install_host, registered_host
host = registered_host()
if host:
return host
install_host(openpype.hosts.resolve.api)
return registered_host()
def launch_menu():
print("Launching Resolve OpenPype menu..")
ensure_installed_host()
openpype.hosts.resolve.api.launch_pype_menu()
def open_file(path):
# Avoid the need to "install" the host
host = ensure_installed_host()
host.open_file(path)
def main():
# Open last workfile
workfile_path = os.environ.get("OPENPYPE_RESOLVE_OPEN_ON_LAUNCH")
if workfile_path:
open_file(workfile_path)
else:
print("No last workfile set to open. Skipping..")
# Launch OpenPype menu
from openpype.settings import get_project_settings
from openpype.pipeline.context_tools import get_current_project_name
project_name = get_current_project_name()
settings = get_project_settings(project_name)
if settings.get("resolve", {}).get("launch_openpype_menu_on_start", True):
launch_menu()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,21 @@
-- Run OpenPype's Python launch script for resolve
function file_exists(name)
local f = io.open(name, "r")
return f ~= nil and io.close(f)
end
openpype_startup_script = os.getenv("OPENPYPE_RESOLVE_STARTUP_SCRIPT")
if openpype_startup_script ~= nil then
script = fusion:MapPath(openpype_startup_script)
if file_exists(script) then
-- We must use RunScript to ensure it runs in a separate
-- process to Resolve itself to avoid a deadlock for
-- certain imports of OpenPype libraries or Qt
print("Running launch script: " .. script)
fusion:RunScript(script)
else
print("Launch script not found at: " .. script)
end
end

View file

@ -29,6 +29,9 @@ def setup(env):
log.info("Utility Scripts Dir: `{}`".format(util_scripts_paths)) log.info("Utility Scripts Dir: `{}`".format(util_scripts_paths))
log.info("Utility Scripts: `{}`".format(scripts)) log.info("Utility Scripts: `{}`".format(scripts))
# Make sure scripts dir exists
os.makedirs(util_scripts_dir, exist_ok=True)
# make sure no script file is in folder # make sure no script file is in folder
for script in os.listdir(util_scripts_dir): for script in os.listdir(util_scripts_dir):
path = os.path.join(util_scripts_dir, script) path = os.path.join(util_scripts_dir, script)
@ -50,6 +53,14 @@ def setup(env):
src = os.path.join(directory, script) src = os.path.join(directory, script)
dst = os.path.join(util_scripts_dir, script) dst = os.path.join(util_scripts_dir, script)
# TODO: Make this a less hacky workaround
if script == "openpype_startup.scriptlib":
# Handle special case for scriptlib that needs to be a folder
# up from the Comp folder in the Fusion scripts
dst = os.path.join(os.path.dirname(util_scripts_dir),
script)
log.info("Copying `{}` to `{}`...".format(src, dst)) log.info("Copying `{}` to `{}`...".format(src, dst))
if os.path.isdir(src): if os.path.isdir(src):
shutil.copytree( shutil.copytree(

View file

@ -35,41 +35,47 @@ class CollectFramesFixDef(
rewrite_version = attribute_values.get("rewrite_version") rewrite_version = attribute_values.get("rewrite_version")
if frames_to_fix: if not frames_to_fix:
instance.data["frames_to_fix"] = frames_to_fix return
subset_name = instance.data["subset"] instance.data["frames_to_fix"] = frames_to_fix
asset_name = instance.data["asset"]
project_entity = instance.data["projectEntity"] subset_name = instance.data["subset"]
project_name = project_entity["name"] asset_name = instance.data["asset"]
version = get_last_version_by_subset_name(project_name, project_entity = instance.data["projectEntity"]
subset_name, project_name = project_entity["name"]
asset_name=asset_name)
if not version:
self.log.warning("No last version found, "
"re-render not possible")
return
representations = get_representations(project_name, version = get_last_version_by_subset_name(
version_ids=[version["_id"]]) project_name,
published_files = [] subset_name,
for repre in representations: asset_name=asset_name
if repre["context"]["family"] not in self.families: )
continue if not version:
self.log.warning(
"No last version found, re-render not possible"
)
return
for file_info in repre.get("files"): representations = get_representations(
published_files.append(file_info["path"]) project_name, version_ids=[version["_id"]]
)
published_files = []
for repre in representations:
if repre["context"]["family"] not in self.families:
continue
instance.data["last_version_published_files"] = published_files for file_info in repre.get("files"):
self.log.debug("last_version_published_files::{}".format( published_files.append(file_info["path"])
instance.data["last_version_published_files"]))
if rewrite_version: instance.data["last_version_published_files"] = published_files
instance.data["version"] = version["name"] self.log.debug("last_version_published_files::{}".format(
# limits triggering version validator instance.data["last_version_published_files"]))
instance.data.pop("latestVersion")
if self.rewrite_version_enable and rewrite_version:
instance.data["version"] = version["name"]
# limits triggering version validator
instance.data.pop("latestVersion")
@classmethod @classmethod
def get_attribute_defs(cls): def get_attribute_defs(cls):

View file

@ -223,6 +223,13 @@
"command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_nuke_tut')", "command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_nuke_tut')",
"tooltip": "Open the OpenPype Nuke user doc page" "tooltip": "Open the OpenPype Nuke user doc page"
}, },
{
"type": "action",
"sourcetype": "python",
"title": "Set Frame Start (Read Node)",
"command": "from openpype.hosts.nuke.startup.frame_setting_for_read_nodes import main;main();",
"tooltip": "Set frame start for read node(s)"
},
{ {
"type": "action", "type": "action",
"sourcetype": "python", "sourcetype": "python",

View file

@ -1,4 +1,5 @@
{ {
"launch_openpype_menu_on_start": false,
"imageio": { "imageio": {
"ocio_config": { "ocio_config": {
"enabled": false, "enabled": false,

View file

@ -5,6 +5,11 @@
"label": "DaVinci Resolve", "label": "DaVinci Resolve",
"is_file": true, "is_file": true,
"children": [ "children": [
{
"type": "boolean",
"key": "launch_openpype_menu_on_start",
"label": "Launch OpenPype menu on start of Resolve"
},
{ {
"key": "imageio", "key": "imageio",
"type": "dict", "type": "dict",

View file

@ -30,7 +30,7 @@ By clicking the icon ```OpenPype Menu``` rolls out.
Choose ```OpenPype Menu > Launcher``` to open the ```Launcher``` window. Choose ```OpenPype Menu > Launcher``` to open the ```Launcher``` window.
When opened you can **choose** the **project** to work in from the list. Then choose the particular **asset** you want to work on then choose **task** When opened you can **choose** the **project** to work in from the list. Then choose the particular **asset** you want to work on then choose **task**
and finally **run 3dsmax by its icon** in the tools. and finally **run 3dsmax by its icon** in the tools.
![Menu OpenPype](assets/3dsmax_tray_OP.png) ![Menu OpenPype](assets/3dsmax_tray_OP.png)
@ -65,13 +65,13 @@ If not any workfile present simply hit ```Save As``` and keep ```Subversion``` e
![Save As Dialog](assets/3dsmax_SavingFirstFile_OP.png) ![Save As Dialog](assets/3dsmax_SavingFirstFile_OP.png)
OpenPype correctly names it and add version to the workfile. This basically happens whenever user trigger ```Save As``` action. Resulting into incremental version numbers like OpenPype correctly names it and add version to the workfile. This basically happens whenever user trigger ```Save As``` action. Resulting into incremental version numbers like
```workfileName_v001``` ```workfileName_v001```
```workfileName_v002``` ```workfileName_v002```
etc. etc.
Basically meaning user is free of guessing what is the correct naming and other necessities to keep everything in order and managed. Basically meaning user is free of guessing what is the correct naming and other necessities to keep everything in order and managed.
@ -105,13 +105,13 @@ Before proceeding further please check [Glossary](artist_concepts.md) and [What
### Intro ### Intro
Current OpenPype integration (ver 3.15.0) supports only ```PointCache``` and ```Camera``` families now. Current OpenPype integration (ver 3.15.0) supports only ```PointCache```, ```Camera```, ```Geometry``` and ```Redshift Proxy``` families now.
**Pointcache** family being basically any geometry outputted as Alembic cache (.abc) format **Pointcache** family being basically any geometry outputted as Alembic cache (.abc) format
**Camera** family being 3dsmax Camera object with/without animation outputted as native .max, FBX, Alembic format **Camera** family being 3dsmax Camera object with/without animation outputted as native .max, FBX, Alembic format
**Redshift Proxy** family being Redshift Proxy object with/without animation outputted as rs format(Redshift Proxy's very own format)
--- ---
:::note Work in progress :::note Work in progress
@ -119,7 +119,3 @@ This part of documentation is still work in progress.
::: :::
## ...to be added ## ...to be added

View file

@ -0,0 +1,61 @@
---
id: dev_blender
title: Blender integration
sidebar_label: Blender integration
toc_max_heading_level: 4
---
## Run python script at launch
In case you need to execute a python script when Blender is started (aka [`-P`](https://docs.blender.org/manual/en/latest/advanced/command_line/arguments.html#python-options)), for example to programmatically modify a blender file for conformation, you can create an OpenPype hook as follows:
```python
from openpype.hosts.blender.hooks import pre_add_run_python_script_arg
from openpype.lib import PreLaunchHook
class MyHook(PreLaunchHook):
"""Add python script to be executed before Blender launch."""
order = pre_add_run_python_script_arg.AddPythonScriptToLaunchArgs.order - 1
app_groups = [
"blender",
]
def execute(self):
self.launch_context.data.setdefault("python_scripts", []).append(
"/path/to/my_script.py"
)
```
You can write a bare python script, as you could run into the [Text Editor](https://docs.blender.org/manual/en/latest/editors/text_editor.html).
### Python script with arguments
#### Adding arguments
In case you need to pass arguments to your script, you can append them to `self.launch_context.data["script_args"]`:
```python
self.launch_context.data.setdefault("script_args", []).append(
"--my-arg",
"value",
)
```
#### Parsing arguments
You can parse arguments in your script using [argparse](https://docs.python.org/3/library/argparse.html) as follows:
```python
import argparse
parser = argparse.ArgumentParser(
description="Parsing arguments for my_script.py"
)
parser.add_argument(
"--my-arg",
nargs="?",
help="My argument",
)
args, unknown = arg_parser.parse_known_args(
sys.argv[sys.argv.index("--") + 1 :]
)
print(args.my_arg)
```

View file

@ -180,6 +180,7 @@ module.exports = {
] ]
}, },
"dev_deadline", "dev_deadline",
"dev_blender",
"dev_colorspace" "dev_colorspace"
] ]
}; };