Merge branch 'develop' into enhancement/AY-5538_workfile-increment-in-one-go

This commit is contained in:
Ondřej Samohel 2024-06-05 15:19:46 +02:00 committed by GitHub
commit acd39c4598
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
327 changed files with 786 additions and 528 deletions

View file

@ -50,10 +50,13 @@ IGNORED_MODULES_IN_AYON = set()
# - this is used to log the missing addon
MOVED_ADDON_MILESTONE_VERSIONS = {
"applications": VersionInfo(0, 2, 0),
"blender": VersionInfo(0, 2, 0),
"celaction": VersionInfo(0, 2, 0),
"clockify": VersionInfo(0, 2, 0),
"flame": VersionInfo(0, 2, 0),
"fusion": VersionInfo(0, 2, 0),
"harmony": VersionInfo(0, 2, 0),
"hiero": VersionInfo(0, 2, 0),
"max": VersionInfo(0, 2, 0),
"photoshop": VersionInfo(0, 2, 0),
"traypublisher": VersionInfo(0, 2, 0),

View file

@ -1,6 +0,0 @@
from .addon import BlenderAddon
__all__ = (
"BlenderAddon",
)

View file

@ -1,10 +0,0 @@
from .addon import (
HIERO_ROOT_DIR,
HieroAddon,
)
__all__ = (
"HIERO_ROOT_DIR",
"HieroAddon",
)

View file

@ -1,4 +1,5 @@
from .version import __version__
from .structures import HostMsgAction
from .webserver_module import (
WebServerAddon
)
@ -7,5 +8,6 @@ from .webserver_module import (
__all__ = (
"__version__",
"HostMsgAction",
"WebServerAddon",
)

View file

@ -9,22 +9,18 @@ from qtpy import QtWidgets
from ayon_core.addon import ITrayService
from ayon_core.tools.stdout_broker.window import ConsoleDialog
from .structures import HostMsgAction
log = logging.getLogger(__name__)
# Host listener icon type
class IconType:
IDLE = "idle"
RUNNING = "running"
FAILED = "failed"
class MsgAction:
CONNECTING = "connecting"
INITIALIZED = "initialized"
ADD = "add"
CLOSE = "close"
class HostListener:
def __init__(self, webserver, module):
self._window_per_id = {}
@ -96,22 +92,22 @@ class HostListener:
if msg.type == aiohttp.WSMsgType.TEXT:
host_name, action, text = self._parse_message(msg)
if action == MsgAction.CONNECTING:
if action == HostMsgAction.CONNECTING:
self._action_per_id[host_name] = None
# must be sent to main thread, or action wont trigger
self.module.execute_in_main_thread(
lambda: self._host_is_connecting(host_name, text))
elif action == MsgAction.CLOSE:
elif action == HostMsgAction.CLOSE:
# clean close
self._close(host_name)
await ws.close()
elif action == MsgAction.INITIALIZED:
elif action == HostMsgAction.INITIALIZED:
self.module.execute_in_main_thread(
# must be queued as _host_is_connecting might not
# be triggered/finished yet
lambda: self._set_host_icon(host_name,
IconType.RUNNING))
elif action == MsgAction.ADD:
elif action == HostMsgAction.ADD:
self.module.execute_in_main_thread(
lambda: self._add_text(host_name, text))
elif msg.type == aiohttp.WSMsgType.ERROR:

View file

@ -0,0 +1,6 @@
# Host listener message actions
class HostMsgAction:
CONNECTING = "connecting"
INITIALIZED = "initialized"
ADD = "add"
CLOSE = "close"

View file

@ -172,12 +172,30 @@ class VersionItem:
def __gt__(self, other):
if not isinstance(other, VersionItem):
return False
if (
other.version == self.version
and self.is_hero
):
# Make sure hero versions are positive
version = abs(self.version)
other_version = abs(other.version)
# Hero version is greater than non-hero
if version == other_version:
return self.is_hero
return version > other_version
def __lt__(self, other):
if not isinstance(other, VersionItem):
return True
return other.version < self.version
# Make sure hero versions are positive
version = abs(self.version)
other_version = abs(other.version)
# Non-hero version is lesser than hero
if version == other_version:
return not self.is_hero
return version < other_version
def __ge__(self, other):
return self.__eq__(other) or self.__gt__(other)
def __le__(self, other):
return self.__eq__(other) or self.__lt__(other)
def to_data(self):
return {

View file

@ -199,7 +199,9 @@ class ProductsModel(QtGui.QStandardItemModel):
product_item = self._product_items_by_id.get(product_id)
if product_item is None:
return None
return list(product_item.version_items.values())
product_items = list(product_item.version_items.values())
product_items.sort(reverse=True)
return product_items
if role == QtCore.Qt.EditRole:
return None

View file

@ -0,0 +1,5 @@
from .broker import StdOutBroker
__all__ = (
"StdOutBroker",
)

View file

@ -1,173 +1,12 @@
import os
import sys
import threading
import collections
import websocket
import json
from datetime import datetime
import warnings
from .broker import StdOutBroker
from ayon_core.lib import Logger
from openpype_modules.webserver.host_console_listener import MsgAction
warnings.warn(
(
"Import of 'StdOutBroker' from 'ayon_core.tools.stdout_broker.app'"
" is deprecated. Please use 'ayon_core.tools.stdout_broker' instead."
),
DeprecationWarning
)
log = Logger.get_logger(__name__)
class StdOutBroker:
"""
Application showing console in Services tray for non python hosts
instead of cmd window.
"""
MAX_LINES = 10000
TIMER_TIMEOUT = 0.200
def __init__(self, host_name):
self.host_name = host_name
self.webserver_client = None
self.original_stdout_write = None
self.original_stderr_write = None
self.log_queue = collections.deque()
date_str = datetime.now().strftime("%d%m%Y%H%M%S")
self.host_id = "{}_{}".format(self.host_name, date_str)
self._std_available = False
self._is_running = False
self._catch_std_outputs()
self._timer = None
@property
def send_to_tray(self):
"""Checks if connected to tray and have access to logs."""
return self.webserver_client and self._std_available
def start(self):
"""Start app, create and start timer"""
if not self._std_available or self._is_running:
return
self._is_running = True
self._create_timer()
self._connect_to_tray()
def stop(self):
"""Disconnect from Tray, process last logs"""
if not self._is_running:
return
self._is_running = False
self._process_queue()
self._disconnect_from_tray()
def host_connected(self):
"""Send to Tray console that host is ready - icon change. """
log.info("Host {} connected".format(self.host_id))
payload = {
"host": self.host_id,
"action": MsgAction.INITIALIZED,
"text": "Integration with {}".format(
str.capitalize(self.host_name))
}
self._send(payload)
def _create_timer(self):
timer = threading.Timer(self.TIMER_TIMEOUT, self._timer_callback)
timer.start()
self._timer = timer
def _timer_callback(self):
if not self._is_running:
return
self._process_queue()
self._create_timer()
def _connect_to_tray(self):
"""Connect to Tray webserver to pass console output. """
if not self._std_available: # not content to log
return
ws = websocket.WebSocket()
webserver_url = os.environ.get("AYON_WEBSERVER_URL")
if not webserver_url:
print("Unknown webserver url, cannot connect to pass log")
return
webserver_url = webserver_url.replace("http", "ws")
ws.connect("{}/ws/host_listener".format(webserver_url))
self.webserver_client = ws
payload = {
"host": self.host_id,
"action": MsgAction.CONNECTING,
"text": "Integration with {}".format(
str.capitalize(self.host_name))
}
self._send(payload)
def _disconnect_from_tray(self):
"""Send to Tray that host is closing - remove from Services. """
print("Host {} closing".format(self.host_name))
if not self.webserver_client:
return
payload = {
"host": self.host_id,
"action": MsgAction.CLOSE,
"text": "Integration with {}".format(
str.capitalize(self.host_name))
}
self._send(payload)
self.webserver_client.close()
def _catch_std_outputs(self):
"""Redirects standard out and error to own functions"""
if sys.stdout:
self.original_stdout_write = sys.stdout.write
sys.stdout.write = self._my_stdout_write
self._std_available = True
if sys.stderr:
self.original_stderr_write = sys.stderr.write
sys.stderr.write = self._my_stderr_write
self._std_available = True
def _my_stdout_write(self, text):
"""Appends outputted text to queue, keep writing to original stdout"""
if self.original_stdout_write is not None:
self.original_stdout_write(text)
if self.send_to_tray:
self.log_queue.append(text)
def _my_stderr_write(self, text):
"""Appends outputted text to queue, keep writing to original stderr"""
if self.original_stderr_write is not None:
self.original_stderr_write(text)
if self.send_to_tray:
self.log_queue.append(text)
def _process_queue(self):
"""Sends lines and purges queue"""
if not self.send_to_tray:
return
lines = tuple(self.log_queue)
self.log_queue.clear()
if lines:
payload = {
"host": self.host_id,
"action": MsgAction.ADD,
"text": "\n".join(lines)
}
self._send(payload)
def _send(self, payload):
"""Worker method to send to existing websocket connection."""
if not self.send_to_tray:
return
try:
self.webserver_client.send(json.dumps(payload))
except ConnectionResetError: # Tray closed
self._connect_to_tray()
__all__ = ("StdOutBroker", )

View file

@ -0,0 +1,174 @@
import os
import sys
import threading
import collections
import json
from datetime import datetime
import websocket
from ayon_core.lib import Logger
from ayon_core.modules.webserver import HostMsgAction
log = Logger.get_logger(__name__)
class StdOutBroker:
"""
Application showing console in Services tray for non python hosts
instead of cmd window.
"""
MAX_LINES = 10000
TIMER_TIMEOUT = 0.200
def __init__(self, host_name):
self.host_name = host_name
self.webserver_client = None
self.original_stdout_write = None
self.original_stderr_write = None
self.log_queue = collections.deque()
date_str = datetime.now().strftime("%d%m%Y%H%M%S")
self.host_id = "{}_{}".format(self.host_name, date_str)
self._std_available = False
self._is_running = False
self._catch_std_outputs()
self._timer = None
@property
def send_to_tray(self):
"""Checks if connected to tray and have access to logs."""
return self.webserver_client and self._std_available
def start(self):
"""Start app, create and start timer"""
if not self._std_available or self._is_running:
return
self._is_running = True
self._create_timer()
self._connect_to_tray()
def stop(self):
"""Disconnect from Tray, process last logs"""
if not self._is_running:
return
self._is_running = False
self._process_queue()
self._disconnect_from_tray()
def host_connected(self):
"""Send to Tray console that host is ready - icon change. """
log.info("Host {} connected".format(self.host_id))
payload = {
"host": self.host_id,
"action": HostMsgAction.INITIALIZED,
"text": "Integration with {}".format(
str.capitalize(self.host_name))
}
self._send(payload)
def _create_timer(self):
timer = threading.Timer(self.TIMER_TIMEOUT, self._timer_callback)
timer.start()
self._timer = timer
def _timer_callback(self):
if not self._is_running:
return
self._process_queue()
self._create_timer()
def _connect_to_tray(self):
"""Connect to Tray webserver to pass console output. """
if not self._std_available: # not content to log
return
ws = websocket.WebSocket()
webserver_url = os.environ.get("AYON_WEBSERVER_URL")
if not webserver_url:
print("Unknown webserver url, cannot connect to pass log")
return
webserver_url = webserver_url.replace("http", "ws")
ws.connect("{}/ws/host_listener".format(webserver_url))
self.webserver_client = ws
payload = {
"host": self.host_id,
"action": HostMsgAction.CONNECTING,
"text": "Integration with {}".format(
str.capitalize(self.host_name))
}
self._send(payload)
def _disconnect_from_tray(self):
"""Send to Tray that host is closing - remove from Services. """
print("Host {} closing".format(self.host_name))
if not self.webserver_client:
return
payload = {
"host": self.host_id,
"action": HostMsgAction.CLOSE,
"text": "Integration with {}".format(
str.capitalize(self.host_name))
}
self._send(payload)
self.webserver_client.close()
def _catch_std_outputs(self):
"""Redirects standard out and error to own functions"""
if sys.stdout:
self.original_stdout_write = sys.stdout.write
sys.stdout.write = self._my_stdout_write
self._std_available = True
if sys.stderr:
self.original_stderr_write = sys.stderr.write
sys.stderr.write = self._my_stderr_write
self._std_available = True
def _my_stdout_write(self, text):
"""Appends outputted text to queue, keep writing to original stdout"""
if self.original_stdout_write is not None:
self.original_stdout_write(text)
if self.send_to_tray:
self.log_queue.append(text)
def _my_stderr_write(self, text):
"""Appends outputted text to queue, keep writing to original stderr"""
if self.original_stderr_write is not None:
self.original_stderr_write(text)
if self.send_to_tray:
self.log_queue.append(text)
def _process_queue(self):
"""Sends lines and purges queue"""
if not self.send_to_tray:
return
lines = tuple(self.log_queue)
self.log_queue.clear()
if lines:
payload = {
"host": self.host_id,
"action": HostMsgAction.ADD,
"text": "\n".join(lines)
}
self._send(payload)
def _send(self, payload):
"""Worker method to send to existing websocket connection."""
if not self.send_to_tray:
return
try:
self.webserver_client.send(json.dumps(payload))
except ConnectionResetError: # Tray closed
self._connect_to_tray()

View file

@ -80,11 +80,11 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
exclude = [
"client/ayon_core/hosts/unreal/integration/*",
"client/ayon_core/hosts/aftereffects/api/extension/js/libs/*",
"client/ayon_core/hosts/hiero/api/startup/*",
"client/ayon_core/modules/deadline/repository/custom/plugins/CelAction/*",
"client/ayon_core/modules/deadline/repository/custom/plugins/HarmonyAYON/*",
"client/ayon_core/modules/click_wrap.py",
"client/ayon_core/scripts/slates/__init__.py"
"client/ayon_core/scripts/slates/__init__.py",
"server_addon/hiero/client/ayon_hiero/api/startup/*"
]
[tool.ruff.lint.per-file-ignores]

View file

@ -0,0 +1,13 @@
from .version import __version__
from .addon import (
BlenderAddon,
BLENDER_ADDON_ROOT,
)
__all__ = (
"__version__",
"BlenderAddon",
"BLENDER_ADDON_ROOT",
)

View file

@ -1,18 +1,21 @@
import os
from ayon_core.addon import AYONAddon, IHostAddon
BLENDER_ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
from .version import __version__
BLENDER_ADDON_ROOT = os.path.dirname(os.path.abspath(__file__))
class BlenderAddon(AYONAddon, IHostAddon):
name = "blender"
version = __version__
host_name = "blender"
def add_implementation_envs(self, env, _app):
"""Modify environments to contain all required for implementation."""
# Prepare path to implementation script
implementation_user_script_path = os.path.join(
BLENDER_ROOT_DIR,
BLENDER_ADDON_ROOT,
"blender_addon"
)
@ -61,7 +64,7 @@ class BlenderAddon(AYONAddon, IHostAddon):
if app.host_name != self.host_name:
return []
return [
os.path.join(BLENDER_ROOT_DIR, "hooks")
os.path.join(BLENDER_ADDON_ROOT, "hooks")
]
def get_workfile_extensions(self):

View file

@ -15,7 +15,6 @@ from .pipeline import (
from .plugin import (
Creator,
Loader,
)
from .workio import (
@ -51,7 +50,6 @@ __all__ = [
"BlenderHost",
"Creator",
"Loader",
# Workfiles API
"open_file",

View file

Before

Width:  |  Height:  |  Size: 632 B

After

Width:  |  Height:  |  Size: 632 B

Before After
Before After

View file

@ -305,7 +305,7 @@ class LaunchCreator(LaunchQtApp):
class LaunchLoader(LaunchQtApp):
"""Launch Avalon Loader."""
"""Launch AYON Loader."""
bl_idname = "wm.avalon_loader"
bl_label = "Load..."

View file

@ -5,9 +5,6 @@ from typing import Callable, Dict, Iterator, List, Optional
import bpy
from . import lib
from . import ops
import pyblish.api
import ayon_api
@ -33,8 +30,12 @@ from ayon_core.lib import (
register_event_callback,
emit_event
)
import ayon_core.hosts.blender
from ayon_core.settings import get_project_settings
from ayon_blender import BLENDER_ADDON_ROOT
from . import lib
from . import ops
from .workio import (
open_file,
save_file,
@ -44,9 +45,7 @@ from .workio import (
work_root,
)
HOST_DIR = os.path.dirname(os.path.abspath(ayon_core.hosts.blender.__file__))
PLUGINS_DIR = os.path.join(HOST_DIR, "plugins")
PLUGINS_DIR = os.path.join(BLENDER_ADDON_ROOT, "plugins")
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")
LOAD_PATH = os.path.join(PLUGINS_DIR, "load")
CREATE_PATH = os.path.join(PLUGINS_DIR, "create")

View file

@ -4,6 +4,7 @@ import itertools
from pathlib import Path
from typing import Dict, List, Optional
import pyblish.api
import bpy
from ayon_core.pipeline import (
@ -13,6 +14,7 @@ from ayon_core.pipeline import (
AVALON_INSTANCE_ID,
AYON_INSTANCE_ID,
)
from ayon_core.pipeline.publish import Extractor
from ayon_core.lib import BoolDef
from .pipeline import (
@ -161,10 +163,23 @@ def deselect_all():
bpy.context.view_layer.objects.active = active
class BaseCreator(Creator):
class BlenderInstancePlugin(pyblish.api.InstancePlugin):
settings_category = "blender"
class BlenderContextPlugin(pyblish.api.ContextPlugin):
settings_category = "blender"
class BlenderExtractor(Extractor):
settings_category = "blender"
class BlenderCreator(Creator):
"""Base class for Blender Creator plug-ins."""
defaults = ['Main']
settings_category = "blender"
create_as_asset_group = False
@staticmethod
@ -265,7 +280,7 @@ class BaseCreator(Creator):
return instance_node
def collect_instances(self):
"""Override abstract method from BaseCreator.
"""Override abstract method from BlenderCreator.
Collect existing instances related to this creator plugin."""
# Cache instances in shared data
@ -292,7 +307,7 @@ class BaseCreator(Creator):
self._add_instance_to_context(instance)
def update_instances(self, update_list):
"""Override abstract method from BaseCreator.
"""Override abstract method from BlenderCreator.
Store changes of existing instances so they can be recollected.
Args:
@ -376,13 +391,7 @@ class BaseCreator(Creator):
]
class Loader(LoaderPlugin):
"""Base class for Loader plug-ins."""
hosts = ["blender"]
class AssetLoader(LoaderPlugin):
class BlenderLoader(LoaderPlugin):
"""A basic AssetLoader for Blender
This will implement the basic logic for linking/appending assets
@ -392,6 +401,7 @@ class AssetLoader(LoaderPlugin):
it's different for different types (e.g. model, rig, animation,
etc.).
"""
settings_category = "blender"
@staticmethod
def _get_instance_empty(instance_name: str, nodes: List) -> Optional[bpy.types.Object]:
@ -496,7 +506,7 @@ class AssetLoader(LoaderPlugin):
# Only containerise if it's not already a collection from a .blend file.
# representation = context["representation"]["name"]
# if representation != "blend":
# from ayon_core.hosts.blender.api.pipeline import containerise
# from ayon_blender.api.pipeline import containerise
# return containerise(
# name=name,
# namespace=namespace,

View file

@ -1,5 +1,5 @@
from ayon_core.pipeline import install_host
from ayon_core.hosts.blender.api import BlenderHost
from ayon_blender.api import BlenderHost
def register():

View file

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Converter for legacy Houdini products."""
from ayon_core.pipeline.create.creator_plugins import ProductConvertorPlugin
from ayon_core.hosts.blender.api.lib import imprint
from ayon_blender.api.lib import imprint
class BlenderLegacyConvertor(ProductConvertorPlugin):
@ -42,7 +42,7 @@ class BlenderLegacyConvertor(ProductConvertorPlugin):
parameter on them.
This is using cached entries done in
:py:meth:`~BaseCreator.cache_instance_data()`
:py:meth:`~BlenderCreator.cache_instance_data()`
"""
self.legacy_instances = self.collection_shared_data.get(

View file

@ -2,10 +2,10 @@
import bpy
from ayon_core.hosts.blender.api import lib, plugin
from ayon_blender.api import lib, plugin
class CreateAction(plugin.BaseCreator):
class CreateAction(plugin.BlenderCreator):
"""Action output for character rigs."""
identifier = "io.openpype.creators.blender.action"

View file

@ -1,9 +1,9 @@
"""Create an animation asset."""
from ayon_core.hosts.blender.api import plugin, lib
from ayon_blender.api import plugin, lib
class CreateAnimation(plugin.BaseCreator):
class CreateAnimation(plugin.BlenderCreator):
"""Animation output for character rigs."""
identifier = "io.openpype.creators.blender.animation"

View file

@ -2,10 +2,10 @@
import bpy
from ayon_core.hosts.blender.api import plugin, lib
from ayon_blender.api import plugin, lib
class CreateBlendScene(plugin.BaseCreator):
class CreateBlendScene(plugin.BlenderCreator):
"""Generic group of assets."""
identifier = "io.openpype.creators.blender.blendscene"

View file

@ -2,11 +2,11 @@
import bpy
from ayon_core.hosts.blender.api import plugin, lib
from ayon_core.hosts.blender.api.pipeline import AVALON_INSTANCES
from ayon_blender.api import plugin, lib
from ayon_blender.api.pipeline import AVALON_INSTANCES
class CreateCamera(plugin.BaseCreator):
class CreateCamera(plugin.BlenderCreator):
"""Polygonal static geometry."""
identifier = "io.openpype.creators.blender.camera"

View file

@ -2,10 +2,10 @@
import bpy
from ayon_core.hosts.blender.api import plugin, lib
from ayon_blender.api import plugin, lib
class CreateLayout(plugin.BaseCreator):
class CreateLayout(plugin.BlenderCreator):
"""Layout output for character rigs."""
identifier = "io.openpype.creators.blender.layout"

View file

@ -2,10 +2,10 @@
import bpy
from ayon_core.hosts.blender.api import plugin, lib
from ayon_blender.api import plugin, lib
class CreateModel(plugin.BaseCreator):
class CreateModel(plugin.BlenderCreator):
"""Polygonal static geometry."""
identifier = "io.openpype.creators.blender.model"

View file

@ -1,9 +1,9 @@
"""Create a pointcache asset."""
from ayon_core.hosts.blender.api import plugin, lib
from ayon_blender.api import plugin, lib
class CreatePointcache(plugin.BaseCreator):
class CreatePointcache(plugin.BlenderCreator):
"""Polygonal static geometry."""
identifier = "io.openpype.creators.blender.pointcache"

View file

@ -2,12 +2,12 @@
import bpy
from ayon_core.lib import version_up
from ayon_core.hosts.blender.api import plugin
from ayon_core.hosts.blender.api.render_lib import prepare_rendering
from ayon_core.hosts.blender.api.workio import save_file
from ayon_blender.api import plugin
from ayon_blender.api.render_lib import prepare_rendering
from ayon_blender.api.workio import save_file
class CreateRenderlayer(plugin.BaseCreator):
class CreateRenderlayer(plugin.BlenderCreator):
"""Single baked camera."""
identifier = "io.openpype.creators.blender.render"

View file

@ -1,9 +1,9 @@
"""Create review."""
from ayon_core.hosts.blender.api import plugin, lib
from ayon_blender.api import plugin, lib
class CreateReview(plugin.BaseCreator):
class CreateReview(plugin.BlenderCreator):
"""Single baked camera."""
identifier = "io.openpype.creators.blender.review"

View file

@ -2,10 +2,10 @@
import bpy
from ayon_core.hosts.blender.api import plugin, lib
from ayon_blender.api import plugin, lib
class CreateRig(plugin.BaseCreator):
class CreateRig(plugin.BlenderCreator):
"""Artist-friendly rig with controls to direct motion."""
identifier = "io.openpype.creators.blender.rig"

View file

@ -1,9 +1,9 @@
"""Create a USD Export."""
from ayon_core.hosts.blender.api import plugin, lib
from ayon_blender.api import plugin, lib
class CreateUSD(plugin.BaseCreator):
class CreateUSD(plugin.BlenderCreator):
"""Create USD Export"""
identifier = "io.openpype.creators.blender.usd"

View file

@ -2,14 +2,14 @@ import bpy
import ayon_api
from ayon_core.pipeline import CreatedInstance, AutoCreator
from ayon_core.hosts.blender.api.plugin import BaseCreator
from ayon_core.hosts.blender.api.pipeline import (
from ayon_blender.api.plugin import BlenderCreator
from ayon_blender.api.pipeline import (
AVALON_PROPERTY,
AVALON_CONTAINERS
)
class CreateWorkfile(BaseCreator, AutoCreator):
class CreateWorkfile(BlenderCreator, AutoCreator):
"""Workfile auto-creator.
The workfile instance stores its data on the `AVALON_CONTAINERS` collection

View file

@ -1,6 +1,6 @@
import bpy
from ayon_core.hosts.blender.api import plugin
from ayon_blender.api import plugin
def append_workfile(context, fname, do_import):
@ -34,7 +34,7 @@ def append_workfile(context, fname, do_import):
collection.children.link(coll)
class AppendBlendLoader(plugin.AssetLoader):
class AppendBlendLoader(plugin.BlenderLoader):
"""Append workfile in Blender (unmanaged)
Warning:
@ -59,7 +59,7 @@ class AppendBlendLoader(plugin.AssetLoader):
return
class ImportBlendLoader(plugin.AssetLoader):
class ImportBlendLoader(plugin.BlenderLoader):
"""Import workfile in the current Blender scene (unmanaged)
Warning:

View file

@ -7,8 +7,8 @@ from typing import Dict, List, Optional
import bpy
from ayon_core.pipeline import get_representation_path
from ayon_core.hosts.blender.api import plugin
from ayon_core.hosts.blender.api.pipeline import (
from ayon_blender.api import plugin
from ayon_blender.api.pipeline import (
containerise_existing,
AVALON_PROPERTY,
)
@ -16,7 +16,7 @@ from ayon_core.hosts.blender.api.pipeline import (
logger = logging.getLogger("ayon").getChild("blender").getChild("load_action")
class BlendActionLoader(plugin.AssetLoader):
class BlendActionLoader(plugin.BlenderLoader):
"""Load action from a .blend file.
Warning:

View file

@ -4,11 +4,11 @@ from typing import Dict, List, Optional
import bpy
from ayon_core.hosts.blender.api import plugin
from ayon_core.hosts.blender.api.pipeline import AVALON_PROPERTY
from ayon_blender.api import plugin
from ayon_blender.api.pipeline import AVALON_PROPERTY
class BlendAnimationLoader(plugin.AssetLoader):
class BlendAnimationLoader(plugin.BlenderLoader):
"""Load animations from a .blend file.
Warning:

View file

@ -10,14 +10,14 @@ from ayon_core.pipeline import (
get_representation_path,
AVALON_CONTAINER_ID,
)
from ayon_core.hosts.blender.api import plugin
from ayon_core.hosts.blender.api.pipeline import (
from ayon_blender.api import plugin
from ayon_blender.api.pipeline import (
AVALON_CONTAINERS,
AVALON_PROPERTY,
)
class AudioLoader(plugin.AssetLoader):
class AudioLoader(plugin.BlenderLoader):
"""Load audio in Blender."""
product_types = {"audio"}

View file

@ -9,15 +9,15 @@ from ayon_core.pipeline import (
registered_host
)
from ayon_core.pipeline.create import CreateContext
from ayon_core.hosts.blender.api import plugin
from ayon_core.hosts.blender.api.lib import imprint
from ayon_core.hosts.blender.api.pipeline import (
from ayon_blender.api import plugin
from ayon_blender.api.lib import imprint
from ayon_blender.api.pipeline import (
AVALON_CONTAINERS,
AVALON_PROPERTY,
)
class BlendLoader(plugin.AssetLoader):
class BlendLoader(plugin.BlenderLoader):
"""Load assets from a .blend file."""
product_types = {"model", "rig", "layout", "camera"}

View file

@ -7,15 +7,15 @@ from ayon_core.pipeline import (
get_representation_path,
AVALON_CONTAINER_ID,
)
from ayon_core.hosts.blender.api import plugin
from ayon_core.hosts.blender.api.lib import imprint
from ayon_core.hosts.blender.api.pipeline import (
from ayon_blender.api import plugin
from ayon_blender.api.lib import imprint
from ayon_blender.api.pipeline import (
AVALON_CONTAINERS,
AVALON_PROPERTY,
)
class BlendSceneLoader(plugin.AssetLoader):
class BlendSceneLoader(plugin.BlenderLoader):
"""Load assets from a .blend file."""
product_types = {"blendScene"}

View file

@ -11,14 +11,14 @@ from ayon_core.pipeline import (
AVALON_CONTAINER_ID,
)
from ayon_core.hosts.blender.api.pipeline import (
from ayon_blender.api.pipeline import (
AVALON_CONTAINERS,
AVALON_PROPERTY,
)
from ayon_core.hosts.blender.api import plugin, lib
from ayon_blender.api import plugin, lib
class CacheModelLoader(plugin.AssetLoader):
class CacheModelLoader(plugin.BlenderLoader):
"""Load cache models.
Stores the imported asset in a collection named after the asset.

View file

@ -10,14 +10,14 @@ from ayon_core.pipeline import (
get_representation_path,
AVALON_CONTAINER_ID,
)
from ayon_core.hosts.blender.api import plugin, lib
from ayon_core.hosts.blender.api.pipeline import (
from ayon_blender.api import plugin, lib
from ayon_blender.api.pipeline import (
AVALON_CONTAINERS,
AVALON_PROPERTY,
)
class AbcCameraLoader(plugin.AssetLoader):
class AbcCameraLoader(plugin.BlenderLoader):
"""Load a camera from Alembic file.
Stores the imported asset in an empty named after the asset.

View file

@ -10,14 +10,14 @@ from ayon_core.pipeline import (
get_representation_path,
AVALON_CONTAINER_ID,
)
from ayon_core.hosts.blender.api import plugin, lib
from ayon_core.hosts.blender.api.pipeline import (
from ayon_blender.api import plugin, lib
from ayon_blender.api.pipeline import (
AVALON_CONTAINERS,
AVALON_PROPERTY,
)
class FbxCameraLoader(plugin.AssetLoader):
class FbxCameraLoader(plugin.BlenderLoader):
"""Load a camera from FBX.
Stores the imported asset in an empty named after the asset.

View file

@ -10,14 +10,14 @@ from ayon_core.pipeline import (
get_representation_path,
AVALON_CONTAINER_ID,
)
from ayon_core.hosts.blender.api import plugin, lib
from ayon_core.hosts.blender.api.pipeline import (
from ayon_blender.api import plugin, lib
from ayon_blender.api.pipeline import (
AVALON_CONTAINERS,
AVALON_PROPERTY,
)
class FbxModelLoader(plugin.AssetLoader):
class FbxModelLoader(plugin.BlenderLoader):
"""Load FBX models.
Stores the imported asset in an empty named after the asset.

View file

@ -15,15 +15,15 @@ from ayon_core.pipeline import (
loaders_from_representation,
AVALON_CONTAINER_ID,
)
from ayon_core.hosts.blender.api.pipeline import (
from ayon_blender.api.pipeline import (
AVALON_INSTANCES,
AVALON_CONTAINERS,
AVALON_PROPERTY,
)
from ayon_core.hosts.blender.api import plugin
from ayon_blender.api import plugin
class JsonLayoutLoader(plugin.AssetLoader):
class JsonLayoutLoader(plugin.BlenderLoader):
"""Load layout published from Unreal."""
product_types = {"layout"}

View file

@ -9,14 +9,14 @@ import json
import bpy
from ayon_core.pipeline import get_representation_path
from ayon_core.hosts.blender.api import plugin
from ayon_core.hosts.blender.api.pipeline import (
from ayon_blender.api import plugin
from ayon_blender.api.pipeline import (
containerise_existing,
AVALON_PROPERTY
)
class BlendLookLoader(plugin.AssetLoader):
class BlendLookLoader(plugin.BlenderLoader):
"""Load models from a .blend file.
Because they come from a .blend file we can simply link the collection that

View file

@ -1,8 +1,8 @@
import pyblish.api
from ayon_core.hosts.blender.api import workio
from ayon_blender.api import workio, plugin
class CollectBlenderCurrentFile(pyblish.api.ContextPlugin):
class CollectBlenderCurrentFile(plugin.BlenderContextPlugin):
"""Inject the current working file into context"""
order = pyblish.api.CollectorOrder - 0.5

View file

@ -3,10 +3,11 @@ import bpy
import pyblish.api
from ayon_core.pipeline.publish import KnownPublishError
from ayon_core.hosts.blender.api.pipeline import AVALON_PROPERTY
from ayon_blender.api import plugin
from ayon_blender.api.pipeline import AVALON_PROPERTY
class CollectBlenderInstanceData(pyblish.api.InstancePlugin):
class CollectBlenderInstanceData(plugin.BlenderInstancePlugin):
"""Validator to verify that the instance is not empty"""
order = pyblish.api.CollectorOrder

View file

@ -5,12 +5,12 @@ import os
import re
import bpy
from ayon_core.hosts.blender.api import colorspace
import pyblish.api
from ayon_blender.api import colorspace, plugin
class CollectBlenderRender(pyblish.api.InstancePlugin):
class CollectBlenderRender(plugin.BlenderInstancePlugin):
"""Gather all publishable render instances."""
order = pyblish.api.CollectorOrder + 0.01

View file

@ -1,9 +1,9 @@
import bpy
import pyblish.api
from ayon_blender.api import plugin
class CollectReview(pyblish.api.InstancePlugin):
class CollectReview(plugin.BlenderInstancePlugin):
"""Collect Review data
"""

View file

@ -1,9 +1,10 @@
from pathlib import Path
from pyblish.api import InstancePlugin, CollectorOrder
from pyblish.api import CollectorOrder
from ayon_blender.api import plugin
class CollectWorkfile(InstancePlugin):
class CollectWorkfile(plugin.BlenderInstancePlugin):
"""Inject workfile data into its instance."""
order = CollectorOrder

View file

@ -4,10 +4,10 @@ import bpy
from ayon_core.lib import BoolDef
from ayon_core.pipeline import publish
from ayon_core.hosts.blender.api import plugin
from ayon_blender.api import plugin
class ExtractABC(publish.Extractor, publish.OptionalPyblishPluginMixin):
class ExtractABC(plugin.BlenderExtractor, publish.OptionalPyblishPluginMixin):
"""Extract as ABC."""
label = "Extract ABC"

View file

@ -3,12 +3,12 @@ import os
import bpy
from ayon_core.pipeline import publish
from ayon_core.hosts.blender.api import plugin
from ayon_blender.api import plugin
class ExtractAnimationABC(
publish.Extractor,
publish.OptionalPyblishPluginMixin,
plugin.BlenderExtractor,
publish.OptionalPyblishPluginMixin,
):
"""Extract as ABC."""

View file

@ -3,9 +3,12 @@ import os
import bpy
from ayon_core.pipeline import publish
from ayon_blender.api import plugin
class ExtractBlend(publish.Extractor, publish.OptionalPyblishPluginMixin):
class ExtractBlend(
plugin.BlenderExtractor, publish.OptionalPyblishPluginMixin
):
"""Extract a blend file."""
label = "Extract Blend"

View file

@ -3,11 +3,12 @@ import os
import bpy
from ayon_core.pipeline import publish
from ayon_blender.api import plugin
class ExtractBlendAnimation(
publish.Extractor,
publish.OptionalPyblishPluginMixin,
plugin.BlenderExtractor,
publish.OptionalPyblishPluginMixin,
):
"""Extract a blend file."""

View file

@ -3,10 +3,12 @@ import os
import bpy
from ayon_core.pipeline import publish
from ayon_core.hosts.blender.api import plugin
from ayon_blender.api import plugin
class ExtractCameraABC(publish.Extractor, publish.OptionalPyblishPluginMixin):
class ExtractCameraABC(
plugin.BlenderExtractor, publish.OptionalPyblishPluginMixin
):
"""Extract camera as ABC."""
label = "Extract Camera (ABC)"

View file

@ -3,10 +3,12 @@ import os
import bpy
from ayon_core.pipeline import publish
from ayon_core.hosts.blender.api import plugin
from ayon_blender.api import plugin
class ExtractCamera(publish.Extractor, publish.OptionalPyblishPluginMixin):
class ExtractCamera(
plugin.BlenderExtractor, publish.OptionalPyblishPluginMixin
):
"""Extract as the camera as FBX."""
label = "Extract Camera (FBX)"

View file

@ -3,10 +3,12 @@ import os
import bpy
from ayon_core.pipeline import publish
from ayon_core.hosts.blender.api import plugin
from ayon_blender.api import plugin
class ExtractFBX(publish.Extractor, publish.OptionalPyblishPluginMixin):
class ExtractFBX(
plugin.BlenderExtractor, publish.OptionalPyblishPluginMixin
):
"""Extract as FBX."""
label = "Extract FBX"

View file

@ -6,8 +6,8 @@ import bpy_extras
import bpy_extras.anim_utils
from ayon_core.pipeline import publish
from ayon_core.hosts.blender.api import plugin
from ayon_core.hosts.blender.api.pipeline import AVALON_PROPERTY
from ayon_blender.api import plugin
from ayon_blender.api.pipeline import AVALON_PROPERTY
def get_all_parents(obj):
@ -42,8 +42,8 @@ def get_highest_root(objects):
class ExtractAnimationFBX(
publish.Extractor,
publish.OptionalPyblishPluginMixin,
plugin.BlenderExtractor,
publish.OptionalPyblishPluginMixin,
):
"""Extract as animation."""

View file

@ -8,11 +8,13 @@ import bpy_extras.anim_utils
from ayon_api import get_representations
from ayon_core.pipeline import publish
from ayon_core.hosts.blender.api import plugin
from ayon_core.hosts.blender.api.pipeline import AVALON_PROPERTY
from ayon_blender.api import plugin
from ayon_blender.api.pipeline import AVALON_PROPERTY
class ExtractLayout(publish.Extractor, publish.OptionalPyblishPluginMixin):
class ExtractLayout(
plugin.BlenderExtractor, publish.OptionalPyblishPluginMixin
):
"""Extract a layout."""
label = "Extract Layout (JSON)"

View file

@ -7,11 +7,13 @@ import pyblish.api
import bpy
from ayon_core.pipeline import publish
from ayon_core.hosts.blender.api import capture
from ayon_core.hosts.blender.api.lib import maintained_time
from ayon_blender.api import capture, plugin
from ayon_blender.api.lib import maintained_time
class ExtractPlayblast(publish.Extractor, publish.OptionalPyblishPluginMixin):
class ExtractPlayblast(
plugin.BlenderExtractor, publish.OptionalPyblishPluginMixin
):
"""
Extract viewport playblast.

View file

@ -3,14 +3,13 @@ import glob
import json
import pyblish.api
from ayon_core.pipeline import publish
from ayon_core.hosts.blender.api import capture
from ayon_core.hosts.blender.api.lib import maintained_time
from ayon_blender.api import capture, plugin
from ayon_blender.api.lib import maintained_time
import bpy
class ExtractThumbnail(publish.Extractor):
class ExtractThumbnail(plugin.BlenderExtractor):
"""Extract viewport thumbnail.
Takes review camera and creates a thumbnail based on viewport

View file

@ -2,11 +2,11 @@ import os
import bpy
from ayon_core.pipeline import publish
from ayon_core.hosts.blender.api import plugin, lib
from ayon_core.pipeline import KnownPublishError
from ayon_blender.api import plugin, lib
class ExtractUSD(publish.Extractor):
class ExtractUSD(plugin.BlenderExtractor):
"""Extract as USD."""
label = "Extract USD"
@ -40,7 +40,7 @@ class ExtractUSD(publish.Extractor):
root = lib.get_highest_root(objects=instance[:])
if not root:
instance_node = instance.data["transientData"]["instance_node"]
raise publish.KnownPublishError(
raise KnownPublishError(
f"No root object found in instance: {instance_node.name}"
)
self.log.debug(f"Exporting using active root: {root.name}")

View file

@ -1,11 +1,12 @@
import pyblish.api
from ayon_core.pipeline.publish import OptionalPyblishPluginMixin
from ayon_core.hosts.blender.api.workio import save_file
from ayon_blender.api.workio import save_file
from ayon_blender.api import plugin
class IncrementWorkfileVersion(
pyblish.api.ContextPlugin,
OptionalPyblishPluginMixin
plugin.BlenderContextPlugin,
OptionalPyblishPluginMixin
):
"""Increment current workfile version."""

View file

@ -2,11 +2,12 @@ import json
import pyblish.api
from ayon_core.pipeline.publish import OptionalPyblishPluginMixin
from ayon_blender.api import plugin
class IntegrateAnimation(
pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin,
plugin.BlenderInstancePlugin,
OptionalPyblishPluginMixin,
):
"""Generate a JSON file for animation."""

View file

@ -2,9 +2,8 @@ from typing import List
import bpy
import pyblish.api
import ayon_core.hosts.blender.api.action
import ayon_blender.api.action
from ayon_blender.api import plugin
from ayon_core.pipeline.publish import (
ValidateContentsOrder,
PublishValidationError,
@ -12,8 +11,10 @@ from ayon_core.pipeline.publish import (
)
class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin):
class ValidateCameraZeroKeyframe(
plugin.BlenderInstancePlugin,
OptionalPyblishPluginMixin
):
"""Camera must have a keyframe at frame 0.
Unreal shifts the first keyframe to frame 0. Forcing the camera to have
@ -25,7 +26,7 @@ class ValidateCameraZeroKeyframe(pyblish.api.InstancePlugin,
hosts = ["blender"]
families = ["camera"]
label = "Zero Keyframe"
actions = [ayon_core.hosts.blender.api.action.SelectInvalidAction]
actions = [ayon_blender.api.action.SelectInvalidAction]
@staticmethod
def get_invalid(instance) -> List:

View file

@ -2,18 +2,20 @@ import os
import bpy
import pyblish.api
from ayon_core.pipeline.publish import (
RepairAction,
ValidateContentsOrder,
PublishValidationError,
OptionalPyblishPluginMixin
)
from ayon_core.hosts.blender.api.render_lib import prepare_rendering
from ayon_blender.api import plugin
from ayon_blender.api.render_lib import prepare_rendering
class ValidateDeadlinePublish(pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin):
class ValidateDeadlinePublish(
plugin.BlenderInstancePlugin,
OptionalPyblishPluginMixin
):
"""Validates Render File Directory is
not the same in every submission
"""

View file

@ -6,6 +6,7 @@ from ayon_core.pipeline.publish import (
OptionalPyblishPluginMixin,
PublishValidationError
)
from ayon_blender.api import plugin
class SaveWorkfileAction(pyblish.api.Action):
@ -18,8 +19,10 @@ class SaveWorkfileAction(pyblish.api.Action):
bpy.ops.wm.avalon_workfiles()
class ValidateFileSaved(pyblish.api.ContextPlugin,
OptionalPyblishPluginMixin):
class ValidateFileSaved(
plugin.BlenderContextPlugin,
OptionalPyblishPluginMixin
):
"""Validate that the workfile has been saved."""
order = pyblish.api.ValidatorOrder - 0.01

View file

@ -1,8 +1,9 @@
import pyblish.api
from ayon_core.pipeline.publish import PublishValidationError
from ayon_blender.api import plugin
class ValidateInstanceEmpty(pyblish.api.InstancePlugin):
class ValidateInstanceEmpty(plugin.BlenderInstancePlugin):
"""Validator to verify that the instance is not empty"""
order = pyblish.api.ValidatorOrder - 0.01

View file

@ -2,19 +2,18 @@ from typing import List
import bpy
import pyblish.api
from ayon_core.pipeline.publish import (
ValidateContentsOrder,
OptionalPyblishPluginMixin,
PublishValidationError
)
import ayon_core.hosts.blender.api.action
import ayon_blender.api.action
from ayon_blender.api import plugin
class ValidateMeshHasUvs(
pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin,
plugin.BlenderInstancePlugin,
OptionalPyblishPluginMixin,
):
"""Validate that the current mesh has UV's."""
@ -22,7 +21,7 @@ class ValidateMeshHasUvs(
hosts = ["blender"]
families = ["model"]
label = "Mesh Has UVs"
actions = [ayon_core.hosts.blender.api.action.SelectInvalidAction]
actions = [ayon_blender.api.action.SelectInvalidAction]
optional = True
@staticmethod

View file

@ -2,25 +2,26 @@ from typing import List
import bpy
import pyblish.api
from ayon_core.pipeline.publish import (
ValidateContentsOrder,
OptionalPyblishPluginMixin,
PublishValidationError
)
import ayon_core.hosts.blender.api.action
import ayon_blender.api.action
from ayon_blender.api import plugin
class ValidateMeshNoNegativeScale(pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin):
class ValidateMeshNoNegativeScale(
plugin.BlenderInstancePlugin,
OptionalPyblishPluginMixin
):
"""Ensure that meshes don't have a negative scale."""
order = ValidateContentsOrder
hosts = ["blender"]
families = ["model"]
label = "Mesh No Negative Scale"
actions = [ayon_core.hosts.blender.api.action.SelectInvalidAction]
actions = [ayon_blender.api.action.SelectInvalidAction]
@staticmethod
def get_invalid(instance) -> List:

View file

@ -3,20 +3,19 @@ from typing import List
import bpy
import pyblish.api
from ayon_core.pipeline.publish import (
ValidateContentsOrder,
OptionalPyblishPluginMixin,
PublishValidationError,
RepairAction
)
import ayon_core.hosts.blender.api.action
import ayon_blender.api.action
from ayon_blender.api import plugin
class ValidateModelMeshUvMap1(
pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin,
plugin.BlenderInstancePlugin,
OptionalPyblishPluginMixin,
):
"""Validate model mesh uvs are named `map1`.
@ -27,7 +26,7 @@ class ValidateModelMeshUvMap1(
hosts = ["blender"]
families = ["model"]
label = "Mesh UVs named map1"
actions = [ayon_core.hosts.blender.api.action.SelectInvalidAction,
actions = [ayon_blender.api.action.SelectInvalidAction,
RepairAction]
optional = True
enabled = False

View file

@ -2,9 +2,8 @@ from typing import List
import bpy
import pyblish.api
import ayon_core.hosts.blender.api.action
import ayon_blender.api.action
from ayon_blender.api import plugin
from ayon_core.pipeline.publish import (
ValidateContentsOrder,
OptionalPyblishPluginMixin,
@ -12,8 +11,10 @@ from ayon_core.pipeline.publish import (
)
class ValidateNoColonsInName(pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin):
class ValidateNoColonsInName(
plugin.BlenderInstancePlugin,
OptionalPyblishPluginMixin
):
"""There cannot be colons in names
Object or bone names cannot include colons. Other software do not
@ -25,7 +26,7 @@ class ValidateNoColonsInName(pyblish.api.InstancePlugin,
hosts = ["blender"]
families = ["model", "rig"]
label = "No Colons in names"
actions = [ayon_core.hosts.blender.api.action.SelectInvalidAction]
actions = [ayon_blender.api.action.SelectInvalidAction]
@staticmethod
def get_invalid(instance) -> List:

View file

@ -7,12 +7,13 @@ from ayon_core.pipeline.publish import (
OptionalPyblishPluginMixin,
PublishValidationError
)
import ayon_core.hosts.blender.api.action
import ayon_blender.api.action
from ayon_blender.api import plugin
class ValidateObjectIsInObjectMode(
pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin,
plugin.BlenderInstancePlugin,
OptionalPyblishPluginMixin,
):
"""Validate that the objects in the instance are in Object Mode."""
@ -20,7 +21,7 @@ class ValidateObjectIsInObjectMode(
hosts = ["blender"]
families = ["model", "rig", "layout"]
label = "Validate Object Mode"
actions = [ayon_core.hosts.blender.api.action.SelectInvalidAction]
actions = [ayon_blender.api.action.SelectInvalidAction]
optional = False
@staticmethod

View file

@ -6,10 +6,13 @@ from ayon_core.pipeline.publish import (
OptionalPyblishPluginMixin,
PublishValidationError
)
from ayon_blender.api import plugin
class ValidateRenderCameraIsSet(pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin):
class ValidateRenderCameraIsSet(
plugin.BlenderInstancePlugin,
OptionalPyblishPluginMixin
):
"""Validate that there is a camera set as active for rendering."""
order = pyblish.api.ValidatorOrder

View file

@ -4,10 +4,8 @@ from typing import List
import mathutils
import bpy
import pyblish.api
from ayon_core.hosts.blender.api import plugin, lib
import ayon_core.hosts.blender.api.action
from ayon_blender.api import plugin, lib
import ayon_blender.api.action
from ayon_core.pipeline.publish import (
ValidateContentsOrder,
OptionalPyblishPluginMixin,
@ -16,15 +14,17 @@ from ayon_core.pipeline.publish import (
)
class ValidateTransformZero(pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin):
class ValidateTransformZero(
plugin.BlenderInstancePlugin,
OptionalPyblishPluginMixin
):
"""Transforms can't have any values"""
order = ValidateContentsOrder
hosts = ["blender"]
families = ["model"]
label = "Transform Zero"
actions = [ayon_core.hosts.blender.api.action.SelectInvalidAction,
actions = [ayon_blender.api.action.SelectInvalidAction,
RepairAction]
_identity = mathutils.Matrix()

View file

@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring AYON addon 'blender' version."""
__version__ = "0.2.0"

View file

@ -1,3 +1,11 @@
name = "blender"
title = "Blender"
version = "0.1.9"
version = "0.2.0"
client_dir = "ayon_blender"
ayon_required_addons = {
"core": ">0.3.2",
}
ayon_compatible_addons = {}

View file

@ -1,3 +1,4 @@
from .version import __version__
from .addon import (
HARMONY_ADDON_ROOT,
HarmonyAddon,
@ -6,6 +7,8 @@ from .addon import (
__all__ = (
"__version__",
"HARMONY_ADDON_ROOT",
"HarmonyAddon",
"get_launch_script_path",

View file

@ -1,11 +1,14 @@
import os
from ayon_core.addon import AYONAddon, IHostAddon
from .version import __version__
HARMONY_ADDON_ROOT = os.path.dirname(os.path.abspath(__file__))
class HarmonyAddon(AYONAddon, IHostAddon):
name = "harmony"
version = __version__
host_name = "harmony"
def add_implementation_envs(self, env, _app):

View file

@ -5,7 +5,7 @@
The easiest way to setup for using Toon Boom Harmony is to use the built-in launch:
```
python -c "import ayon_core.hosts.harmony.api as harmony;harmony.launch("path/to/harmony/executable")"
python -c "import ayon_harmony.api as harmony;harmony.launch("path/to/harmony/executable")"
```
Communication with Harmony happens with a server/client relationship where the server is in the Python process and the client is in the Harmony process. Messages between Python and Harmony are required to be dictionaries, which are serialized to strings:
@ -59,7 +59,7 @@ You can show the Workfiles app when Harmony launches by setting environment vari
### Low level messaging
To send from Python to Harmony you can use the exposed method:
```python
import ayon_core.hosts.harmony.api as harmony
import ayon_harmony.api as harmony
from uuid import uuid4
@ -75,7 +75,7 @@ print(harmony.send({"function": func, "args": ["Python"]})["result"])
To send a function with multiple arguments its best to declare the arguments within the function:
```python
import ayon_core.hosts.harmony.api as harmony
import ayon_harmony.api as harmony
from uuid import uuid4
signature = str(uuid4()).replace("-", "_")
@ -114,7 +114,7 @@ PypeHarmony.myAwesomeFunction = function() {
Then you can call that javascript code from your Python like:
```Python
import ayon_core.hosts.harmony.api as harmony
import ayon_harmony.api as harmony
harmony.send({"function": "PypeHarmony.myAwesomeFunction"});
@ -159,7 +159,7 @@ Now in python, just read all those files and send them to Harmony.
```python
from pathlib import Path
import ayon_core.hosts.harmony.api as harmony
import ayon_harmony.api as harmony
path_to_js = Path('/path/to/my/js')
script_to_send = ""
@ -178,7 +178,7 @@ harmony.send({"function": "Master.Boo.B"})
### Scene Save
Instead of sending a request to Harmony with `scene.saveAll` please use:
```python
import ayon_core.hosts.harmony.api as harmony
import ayon_harmony.api as harmony
harmony.save_scene()
```
@ -195,7 +195,7 @@ These plugins were made with the [polly config](https://github.com/mindbender-st
#### Creator Plugin
```python
import ayon_core.hosts.harmony.api as harmony
import ayon_harmony.api as harmony
from uuid import uuid4
@ -213,7 +213,7 @@ class CreateComposite(harmony.Creator):
The creator plugin can be configured to use other node types. For example here is a write node creator:
```python
from uuid import uuid4
import ayon_core.hosts.harmony.api as harmony
import ayon_harmony.api as harmony
class CreateRender(harmony.Creator):
@ -244,7 +244,7 @@ class CreateRender(harmony.Creator):
```python
import pyblish.api
from ayon_core.pipeline import AYON_INSTANCE_ID, AVALON_INSTANCE_ID
import ayon_core.hosts.harmony.api as harmony
import ayon_harmony.api as harmony
class CollectInstances(pyblish.api.ContextPlugin):
@ -292,7 +292,7 @@ import os
from uuid import uuid4
import pyblish.api
import ayon_core.hosts.harmony.api as harmony
import ayon_harmony.api as harmony
import clique
@ -423,7 +423,7 @@ class ExtractImage(pyblish.api.InstancePlugin):
import os
from uuid import uuid4
import ayon_core.hosts.harmony.api as harmony
import ayon_harmony.api as harmony
signature = str(uuid4()).replace("-", "_")
copy_files = """function copyFile(srcFilename, dstFilename)

View file

@ -387,7 +387,7 @@ function start() {
*/
self.onCreator = function() {
app.avalonClient.send({
'module': 'ayon_core.hosts.harmony.api.lib',
'module': 'ayon_harmony.api.lib',
'method': 'show',
'args': ['creator']
}, false);
@ -402,7 +402,7 @@ function start() {
*/
self.onWorkfiles = function() {
app.avalonClient.send({
'module': 'ayon_core.hosts.harmony.api.lib',
'module': 'ayon_harmony.api.lib',
'method': 'show',
'args': ['workfiles']
}, false);
@ -417,7 +417,7 @@ function start() {
*/
self.onLoad = function() {
app.avalonClient.send({
'module': 'ayon_core.hosts.harmony.api.lib',
'module': 'ayon_harmony.api.lib',
'method': 'show',
'args': ['loader']
}, false);
@ -433,7 +433,7 @@ function start() {
*/
self.onPublish = function() {
app.avalonClient.send({
'module': 'ayon_core.hosts.harmony.api.lib',
'module': 'ayon_harmony.api.lib',
'method': 'show',
'args': ['publish']
}, false);
@ -449,7 +449,7 @@ function start() {
*/
self.onManage = function() {
app.avalonClient.send({
'module': 'ayon_core.hosts.harmony.api.lib',
'module': 'ayon_harmony.api.lib',
'method': 'show',
'args': ['sceneinventory']
}, false);
@ -465,7 +465,7 @@ function start() {
*/
self.onSubsetManage = function() {
app.avalonClient.send({
'module': 'ayon_core.hosts.harmony.api.lib',
'module': 'ayon_harmony.api.lib',
'method': 'show',
'args': ['subsetmanager']
}, false);
@ -482,7 +482,7 @@ function start() {
self.onSetSceneSettings = function() {
app.avalonClient.send(
{
"module": "ayon_core.hosts.harmony.api",
"module": "ayon_harmony.api",
"method": "ensure_scene_settings",
"args": []
},
@ -500,7 +500,7 @@ function start() {
*/
self.onExperimentalTools = function() {
app.avalonClient.send({
'module': 'ayon_core.hosts.harmony.api.lib',
'module': 'ayon_harmony.api.lib',
'method': 'show',
'args': ['experimental_tools']
}, false);
@ -550,7 +550,7 @@ function ensureSceneSettings() {
var app = QCoreApplication.instance();
app.avalonClient.send(
{
"module": "ayon_core.hosts.harmony.api",
"module": "ayon_harmony.api",
"method": "ensure_scene_settings",
"args": []
},

View file

@ -8,7 +8,7 @@ workfile or others.
import os
import sys
from ayon_core.hosts.harmony.api.lib import main as host_main
from ayon_harmony.api.lib import main as host_main
# Get current file to locate start point of sys.argv
CURRENT_FILE = os.path.abspath(__file__)

View file

@ -20,7 +20,7 @@ import collections
from qtpy import QtWidgets, QtCore, QtGui
from ayon_core.lib import is_using_ayon_console
from ayon_core.tools.stdout_broker.app import StdOutBroker
from ayon_core.tools.stdout_broker import StdOutBroker
from ayon_core.tools.utils import host_tools
from ayon_core import style
@ -186,7 +186,7 @@ def launch(application_path, *args):
"""
from ayon_core.pipeline import install_host
from ayon_core.hosts.harmony import api as harmony
from ayon_harmony import api as harmony
install_host(harmony)
@ -486,7 +486,7 @@ def imprint(node_id, data, remove=False):
remove (bool): Removes the data from the scene.
Example:
>>> from ayon_core.hosts.harmony.api import lib
>>> from ayon_harmony.api import lib
>>> node = "Top/Display"
>>> data = {"str": "something", "int": 1, "float": 0.32, "bool": True}
>>> lib.imprint(layer, data)

View file

@ -15,11 +15,11 @@ from ayon_core.pipeline import (
from ayon_core.pipeline.load import get_outdated_containers
from ayon_core.pipeline.context_tools import get_current_folder_entity
from ayon_core.hosts.harmony import HARMONY_ADDON_ROOT
import ayon_core.hosts.harmony.api as harmony
from ayon_harmony import HARMONY_ADDON_ROOT
import ayon_harmony.api as harmony
log = logging.getLogger("ayon_core.hosts.harmony")
log = logging.getLogger("ayon_harmony")
PLUGINS_DIR = os.path.join(HARMONY_ADDON_ROOT, "plugins")
PUBLISH_PATH = os.path.join(PLUGINS_DIR, "publish")

View file

@ -1,5 +1,5 @@
from ayon_core.pipeline import LegacyCreator
import ayon_core.hosts.harmony.api as harmony
import ayon_harmony.api as harmony
class Creator(LegacyCreator):

View file

@ -69,6 +69,8 @@ class Server(threading.Thread):
self.log.debug(
f"[{self.timestamp()}] Processing request:\n{pretty}")
# TODO javascript should not define which module is imported and
# which function is called. It should send predefined requests.
try:
module = importlib.import_module(request["module"])
method = getattr(module, request["method"])

Some files were not shown because too many files have changed in this diff Show more