Skip to content
This repository has been archived by the owner on Sep 20, 2024. It is now read-only.

Fusion: New Publisher #3892

Merged
merged 36 commits into from
Mar 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
7b7085f
Merge remote-tracking branch 'upstream/develop' into fusion_new_publi…
BigRoy Sep 20, 2022
2ace936
Draft to refactor fusion to new publisher and use as FusionHost
BigRoy Sep 20, 2022
058af38
Merge branch 'fusion_integration_v2' into fusion_new_publisher
BigRoy Sep 20, 2022
13cb1f4
Ensure newline
BigRoy Sep 20, 2022
218c836
Reorder logic - makes more sense to check id first
BigRoy Sep 21, 2022
1cf86a6
Remove creator in menu in favor of new publisher
BigRoy Sep 21, 2022
ae5c565
Fix logic - add comments that these will remain unused however
BigRoy Sep 21, 2022
d62e1ee
Continue refactor to new publisher
BigRoy Sep 21, 2022
33cf1c3
Cleanup and fixes
BigRoy Sep 21, 2022
450a471
Rename `create_exr_saver.py` -> `create_saver.py`
BigRoy Sep 21, 2022
7a046dd
Register the saver directly on create
BigRoy Sep 21, 2022
cd08257
Fix keyword
BigRoy Sep 21, 2022
01167e8
Barebones refactor of validators to raise PublishValidationError
BigRoy Sep 21, 2022
ce95465
Fix bugs in Creator
BigRoy Sep 21, 2022
af8662c
Fix refactor to ContextPlugin
BigRoy Sep 21, 2022
fe857b8
Add title to error
BigRoy Sep 21, 2022
6abfabe
Fix missing import
BigRoy Sep 21, 2022
735a7cc
Merge remote-tracking branch 'upstream/feature/new_publisher_proposal…
BigRoy Sep 23, 2022
80336a7
Merge remote-tracking branch 'upstream/feature/new_publisher_proposal…
BigRoy Sep 23, 2022
bdfe241
Refactor new publish logic to make use of "transientData" on the Creator
BigRoy Sep 23, 2022
87621c1
Refactor `INewPublisher` to `IPublishHost`
BigRoy Sep 23, 2022
11bd9e8
Add Select Invalid Action
BigRoy Sep 23, 2022
0f1ed03
Add Select Invalid action to more validators
BigRoy Sep 23, 2022
88e0ccf
Merge remote-tracking branch 'upstream/develop' into fusion_new_publi…
BigRoy Sep 26, 2022
5cda77f
Merge branch 'fusion_new_publish_transient_data' into fusion_new_publ…
BigRoy Sep 26, 2022
a64a551
Tweak label
BigRoy Sep 27, 2022
85cb398
Implement draft for Create Workfile
BigRoy Sep 27, 2022
f555c1b
Shush hound
BigRoy Sep 27, 2022
b8f4a0a
Specifiy families explicitly (to avoid issues with workfile family no…
BigRoy Sep 27, 2022
2afc8ba
Fix collector with workfile present
BigRoy Sep 27, 2022
d0ea917
Remove storage of empty tool placeholder value
BigRoy Sep 27, 2022
3a952d5
Cosmetics/readability
BigRoy Sep 27, 2022
00d9aa2
Apply to workfile family - like other hosts do
BigRoy Sep 27, 2022
34c1346
Refactor filename
BigRoy Sep 27, 2022
d608059
Include workfile family
BigRoy Sep 27, 2022
eed6575
Move Submit Fusion Deadline to Deadline Module
BigRoy Sep 27, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 4 additions & 23 deletions openpype/hosts/fusion/api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,11 @@
from .pipeline import (
install,
uninstall,

FusionHost,
ls,

imprint_container,
parse_container
)

from .workio import (
open_file,
save_file,
current_file,
has_unsaved_changes,
file_extensions,
work_root
parse_container,
list_instances,
remove_instance
)

from .lib import (
Expand All @@ -30,21 +21,11 @@

__all__ = [
# pipeline
"install",
"uninstall",
"ls",

"imprint_container",
"parse_container",

# workio
"open_file",
"save_file",
"current_file",
"has_unsaved_changes",
"file_extensions",
"work_root",

# lib
"maintained_selection",
"update_frame_range",
Expand Down
54 changes: 54 additions & 0 deletions openpype/hosts/fusion/api/action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import pyblish.api


from openpype.hosts.fusion.api.lib import get_current_comp
from openpype.pipeline.publish import get_errored_instances_from_context


class SelectInvalidAction(pyblish.api.Action):
"""Select invalid nodes in Maya when plug-in failed.

To retrieve the invalid nodes this assumes a static `get_invalid()`
method is available on the plugin.

"""
label = "Select invalid"
on = "failed" # This action is only available on a failed plug-in
icon = "search" # Icon from Awesome Icon

def process(self, context, plugin):
errored_instances = get_errored_instances_from_context(context)

# Apply pyblish.logic to get the instances for the plug-in
instances = pyblish.api.instances_by_plugin(errored_instances, plugin)

# Get the invalid nodes for the plug-ins
self.log.info("Finding invalid nodes..")
invalid = list()
for instance in instances:
invalid_nodes = plugin.get_invalid(instance)
if invalid_nodes:
if isinstance(invalid_nodes, (list, tuple)):
invalid.extend(invalid_nodes)
else:
self.log.warning("Plug-in returned to be invalid, "
"but has no selectable nodes.")

if not invalid:
# Assume relevant comp is current comp and clear selection
self.log.info("No invalid tools found.")
comp = get_current_comp()
flow = comp.CurrentFrame.FlowView
flow.Select() # No args equals clearing selection
return

# Assume a single comp
first_tool = invalid[0]
comp = first_tool.Comp()
flow = comp.CurrentFrame.FlowView
flow.Select() # No args equals clearing selection
names = set()
for tool in invalid:
flow.Select(tool, True)
names.add(tool.Name)
self.log.info("Selecting invalid tools: %s" % ", ".join(sorted(names)))
8 changes: 1 addition & 7 deletions openpype/hosts/fusion/api/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ def __init__(self, *args, **kwargs):
asset_label.setAlignment(QtCore.Qt.AlignHCenter)

workfiles_btn = QtWidgets.QPushButton("Workfiles...", self)
create_btn = QtWidgets.QPushButton("Create...", self)
publish_btn = QtWidgets.QPushButton("Publish...", self)
load_btn = QtWidgets.QPushButton("Load...", self)
manager_btn = QtWidgets.QPushButton("Manage...", self)
Expand All @@ -75,7 +74,6 @@ def __init__(self, *args, **kwargs):

layout.addSpacing(20)

layout.addWidget(create_btn)
layout.addWidget(load_btn)
layout.addWidget(publish_btn)
layout.addWidget(manager_btn)
Expand All @@ -100,7 +98,6 @@ def __init__(self, *args, **kwargs):
self.asset_label = asset_label

workfiles_btn.clicked.connect(self.on_workfile_clicked)
create_btn.clicked.connect(self.on_create_clicked)
publish_btn.clicked.connect(self.on_publish_clicked)
load_btn.clicked.connect(self.on_load_clicked)
manager_btn.clicked.connect(self.on_manager_clicked)
Expand Down Expand Up @@ -140,11 +137,8 @@ def deregister_all_callbacks(self):
def on_workfile_clicked(self):
host_tools.show_workfiles()

def on_create_clicked(self):
host_tools.show_creator()

def on_publish_clicked(self):
host_tools.show_publish()
host_tools.show_publisher()

def on_load_clicked(self):
host_tools.show_loader(use_context=True)
Expand Down
193 changes: 132 additions & 61 deletions openpype/hosts/fusion/api/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
Basic avalon integration
"""
import os
import sys
import logging
import contextlib

import pyblish.api

Expand All @@ -14,15 +16,14 @@
register_loader_plugin_path,
register_creator_plugin_path,
register_inventory_action_path,
deregister_loader_plugin_path,
deregister_creator_plugin_path,
deregister_inventory_action_path,
AVALON_CONTAINER_ID,
)
from openpype.pipeline.load import any_outdated_containers
from openpype.hosts.fusion import FUSION_HOST_DIR
from openpype.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost
from openpype.tools.utils import host_tools


from .lib import (
get_current_comp,
comp_lock_and_undo_chunk,
Expand All @@ -47,71 +48,99 @@ def emit(self, record):
comp.Print(entry)


def install():
"""Install fusion-specific functionality of OpenPype.
class FusionHost(HostBase, IWorkfileHost, ILoadHost, IPublishHost):
name = "fusion"

This is where you install menus and register families, data
and loaders into fusion.
def install(self):
"""Install fusion-specific functionality of OpenPype.

It is called automatically when installing via
`openpype.pipeline.install_host(openpype.hosts.fusion.api)`
This is where you install menus and register families, data
and loaders into fusion.

See the Maya equivalent for inspiration on how to implement this.
It is called automatically when installing via
`openpype.pipeline.install_host(openpype.hosts.fusion.api)`

"""
# Remove all handlers associated with the root logger object, because
# that one always logs as "warnings" incorrectly.
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)

# Attach default logging handler that prints to active comp
logger = logging.getLogger()
formatter = logging.Formatter(fmt="%(message)s\n")
handler = CompLogHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

pyblish.api.register_host("fusion")
pyblish.api.register_plugin_path(PUBLISH_PATH)
log.info("Registering Fusion plug-ins..")

register_loader_plugin_path(LOAD_PATH)
register_creator_plugin_path(CREATE_PATH)
register_inventory_action_path(INVENTORY_PATH)

pyblish.api.register_callback(
"instanceToggled", on_pyblish_instance_toggled
)

# Fusion integration currently does not attach to direct callbacks of
# the application. So we use workfile callbacks to allow similar behavior
# on save and open
register_event_callback("workfile.open.after", on_after_open)


def uninstall():
"""Uninstall all that was installed

This is where you undo everything that was done in `install()`.
That means, removing menus, deregistering families and data
and everything. It should be as though `install()` was never run,
because odds are calling this function means the user is interested
in re-installing shortly afterwards. If, for example, he has been
modifying the menu or registered families.
See the Maya equivalent for inspiration on how to implement this.

"""
pyblish.api.deregister_host("fusion")
pyblish.api.deregister_plugin_path(PUBLISH_PATH)
log.info("Deregistering Fusion plug-ins..")
"""
# Remove all handlers associated with the root logger object, because
# that one always logs as "warnings" incorrectly.
for handler in logging.root.handlers[:]:
logging.root.removeHandler(handler)

# Attach default logging handler that prints to active comp
logger = logging.getLogger()
formatter = logging.Formatter(fmt="%(message)s\n")
handler = CompLogHandler()
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

pyblish.api.register_host("fusion")
pyblish.api.register_plugin_path(PUBLISH_PATH)
log.info("Registering Fusion plug-ins..")

register_loader_plugin_path(LOAD_PATH)
register_creator_plugin_path(CREATE_PATH)
register_inventory_action_path(INVENTORY_PATH)

pyblish.api.register_callback("instanceToggled",
on_pyblish_instance_toggled)

# Fusion integration currently does not attach to direct callbacks of
# the application. So we use workfile callbacks to allow similar
# behavior on save and open
register_event_callback("workfile.open.after", on_after_open)

# region workfile io api
def has_unsaved_changes(self):
comp = get_current_comp()
return comp.GetAttrs()["COMPB_Modified"]

def get_workfile_extensions(self):
return [".comp"]

def save_workfile(self, dst_path=None):
comp = get_current_comp()
comp.Save(dst_path)

def open_workfile(self, filepath):
# Hack to get fusion, see
# openpype.hosts.fusion.api.pipeline.get_current_comp()
fusion = getattr(sys.modules["__main__"], "fusion", None)

return fusion.LoadComp(filepath)

def get_current_workfile(self):
comp = get_current_comp()
current_filepath = comp.GetAttrs()["COMPS_FileName"]
if not current_filepath:
return None

return current_filepath

def work_root(self, session):
work_dir = session["AVALON_WORKDIR"]
scene_dir = session.get("AVALON_SCENEDIR")
if scene_dir:
return os.path.join(work_dir, scene_dir)
else:
return work_dir
# endregion

deregister_loader_plugin_path(LOAD_PATH)
deregister_creator_plugin_path(CREATE_PATH)
deregister_inventory_action_path(INVENTORY_PATH)
@contextlib.contextmanager
def maintained_selection(self):
from .lib import maintained_selection
return maintained_selection()

pyblish.api.deregister_callback(
"instanceToggled", on_pyblish_instance_toggled
)
def get_containers(self):
return ls()

def update_context_data(self, data, changes):
print(data, changes)

def get_context_data(self):
return {}


def on_pyblish_instance_toggled(instance, old_value, new_value):
Expand Down Expand Up @@ -254,3 +283,45 @@ def parse_container(tool):
return container


# TODO: Function below is currently unused prototypes
def list_instances(creator_id=None):
"""Return created instances in current workfile which will be published.

Returns:
(list) of dictionaries matching instances format
"""

comp = get_current_comp()
tools = comp.GetToolList(False).values()

instance_signature = {
"id": "pyblish.avalon.instance",
"identifier": creator_id
}
instances = []
for tool in tools:

data = tool.GetData('openpype')
if not isinstance(data, dict):
continue

if data.get("id") != instance_signature["id"]:
continue

if creator_id and data.get("identifier") != creator_id:
continue

instances.append(tool)

return instances


# TODO: Function below is currently unused prototypes
def remove_instance(instance):
"""Remove instance from current workfile.

Args:
instance (dict): instance representation from subsetmanager model
"""
# Assume instance is a Fusion tool directly
instance["tool"].Delete()
Loading