mirror of
https://github.com/ynput/ayon-core.git
synced 2025-12-24 21:04:40 +01:00
[Automated] Merged develop into main
This commit is contained in:
commit
7a4a4876da
50 changed files with 1517 additions and 280 deletions
|
|
@ -5,11 +5,8 @@ def add_implementation_envs(env, _app):
|
|||
"""Modify environments to contain all required for implementation."""
|
||||
# Prepare path to implementation script
|
||||
implementation_user_script_path = os.path.join(
|
||||
os.environ["OPENPYPE_REPOS_ROOT"],
|
||||
"repos",
|
||||
"avalon-core",
|
||||
"setup",
|
||||
"blender"
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"blender_addon"
|
||||
)
|
||||
|
||||
# Add blender implementation script path to PYTHONPATH
|
||||
|
|
|
|||
|
|
@ -1,94 +1,64 @@
|
|||
import os
|
||||
import sys
|
||||
import traceback
|
||||
"""Public API
|
||||
|
||||
import bpy
|
||||
Anything that isn't defined here is INTERNAL and unreliable for external use.
|
||||
|
||||
from .lib import append_user_scripts
|
||||
"""
|
||||
|
||||
from avalon import api as avalon
|
||||
from pyblish import api as pyblish
|
||||
from .pipeline import (
|
||||
install,
|
||||
uninstall,
|
||||
ls,
|
||||
publish,
|
||||
containerise,
|
||||
)
|
||||
|
||||
import openpype.hosts.blender
|
||||
from .plugin import (
|
||||
Creator,
|
||||
Loader,
|
||||
)
|
||||
|
||||
HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.blender.__file__))
|
||||
PLUGINS_DIR = os.path.join(HOST_DIR, "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")
|
||||
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")
|
||||
from .workio import (
|
||||
open_file,
|
||||
save_file,
|
||||
current_file,
|
||||
has_unsaved_changes,
|
||||
file_extensions,
|
||||
work_root,
|
||||
)
|
||||
|
||||
ORIGINAL_EXCEPTHOOK = sys.excepthook
|
||||
from .lib import (
|
||||
lsattr,
|
||||
lsattrs,
|
||||
read,
|
||||
maintained_selection,
|
||||
get_selection,
|
||||
# unique_name,
|
||||
)
|
||||
|
||||
|
||||
def pype_excepthook_handler(*args):
|
||||
traceback.print_exception(*args)
|
||||
__all__ = [
|
||||
"install",
|
||||
"uninstall",
|
||||
"ls",
|
||||
"publish",
|
||||
"containerise",
|
||||
|
||||
"Creator",
|
||||
"Loader",
|
||||
|
||||
def install():
|
||||
"""Install Blender configuration for Avalon."""
|
||||
sys.excepthook = pype_excepthook_handler
|
||||
pyblish.register_plugin_path(str(PUBLISH_PATH))
|
||||
avalon.register_plugin_path(avalon.Loader, str(LOAD_PATH))
|
||||
avalon.register_plugin_path(avalon.Creator, str(CREATE_PATH))
|
||||
append_user_scripts()
|
||||
avalon.on("new", on_new)
|
||||
avalon.on("open", on_open)
|
||||
# Workfiles API
|
||||
"open_file",
|
||||
"save_file",
|
||||
"current_file",
|
||||
"has_unsaved_changes",
|
||||
"file_extensions",
|
||||
"work_root",
|
||||
|
||||
|
||||
def uninstall():
|
||||
"""Uninstall Blender configuration for Avalon."""
|
||||
sys.excepthook = ORIGINAL_EXCEPTHOOK
|
||||
pyblish.deregister_plugin_path(str(PUBLISH_PATH))
|
||||
avalon.deregister_plugin_path(avalon.Loader, str(LOAD_PATH))
|
||||
avalon.deregister_plugin_path(avalon.Creator, str(CREATE_PATH))
|
||||
|
||||
|
||||
def set_start_end_frames():
|
||||
from avalon import io
|
||||
|
||||
asset_name = io.Session["AVALON_ASSET"]
|
||||
asset_doc = io.find_one({
|
||||
"type": "asset",
|
||||
"name": asset_name
|
||||
})
|
||||
|
||||
scene = bpy.context.scene
|
||||
|
||||
# Default scene settings
|
||||
frameStart = scene.frame_start
|
||||
frameEnd = scene.frame_end
|
||||
fps = scene.render.fps
|
||||
resolution_x = scene.render.resolution_x
|
||||
resolution_y = scene.render.resolution_y
|
||||
|
||||
# Check if settings are set
|
||||
data = asset_doc.get("data")
|
||||
|
||||
if not data:
|
||||
return
|
||||
|
||||
if data.get("frameStart"):
|
||||
frameStart = data.get("frameStart")
|
||||
if data.get("frameEnd"):
|
||||
frameEnd = data.get("frameEnd")
|
||||
if data.get("fps"):
|
||||
fps = data.get("fps")
|
||||
if data.get("resolutionWidth"):
|
||||
resolution_x = data.get("resolutionWidth")
|
||||
if data.get("resolutionHeight"):
|
||||
resolution_y = data.get("resolutionHeight")
|
||||
|
||||
scene.frame_start = frameStart
|
||||
scene.frame_end = frameEnd
|
||||
scene.render.fps = fps
|
||||
scene.render.resolution_x = resolution_x
|
||||
scene.render.resolution_y = resolution_y
|
||||
|
||||
|
||||
def on_new(arg1, arg2):
|
||||
set_start_end_frames()
|
||||
|
||||
|
||||
def on_open(arg1, arg2):
|
||||
set_start_end_frames()
|
||||
# Utility functions
|
||||
"maintained_selection",
|
||||
"lsattr",
|
||||
"lsattrs",
|
||||
"read",
|
||||
"get_selection",
|
||||
# "unique_name",
|
||||
]
|
||||
|
|
|
|||
BIN
openpype/hosts/blender/api/icons/pyblish-32x32.png
Normal file
BIN
openpype/hosts/blender/api/icons/pyblish-32x32.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 632 B |
|
|
@ -1,9 +1,16 @@
|
|||
import os
|
||||
import traceback
|
||||
import importlib
|
||||
import contextlib
|
||||
from typing import Dict, List, Union
|
||||
|
||||
import bpy
|
||||
import addon_utils
|
||||
from openpype.api import Logger
|
||||
|
||||
from . import pipeline
|
||||
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
|
||||
def load_scripts(paths):
|
||||
|
|
@ -125,3 +132,155 @@ def append_user_scripts():
|
|||
except Exception:
|
||||
print("Couldn't load user scripts \"{}\"".format(user_scripts))
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
def imprint(node: bpy.types.bpy_struct_meta_idprop, data: Dict):
|
||||
r"""Write `data` to `node` as userDefined attributes
|
||||
|
||||
Arguments:
|
||||
node: Long name of node
|
||||
data: Dictionary of key/value pairs
|
||||
|
||||
Example:
|
||||
>>> import bpy
|
||||
>>> def compute():
|
||||
... return 6
|
||||
...
|
||||
>>> bpy.ops.mesh.primitive_cube_add()
|
||||
>>> cube = bpy.context.view_layer.objects.active
|
||||
>>> imprint(cube, {
|
||||
... "regularString": "myFamily",
|
||||
... "computedValue": lambda: compute()
|
||||
... })
|
||||
...
|
||||
>>> cube['avalon']['computedValue']
|
||||
6
|
||||
"""
|
||||
|
||||
imprint_data = dict()
|
||||
|
||||
for key, value in data.items():
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
if callable(value):
|
||||
# Support values evaluated at imprint
|
||||
value = value()
|
||||
|
||||
if not isinstance(value, (int, float, bool, str, list)):
|
||||
raise TypeError(f"Unsupported type: {type(value)}")
|
||||
|
||||
imprint_data[key] = value
|
||||
|
||||
pipeline.metadata_update(node, imprint_data)
|
||||
|
||||
|
||||
def lsattr(attr: str,
|
||||
value: Union[str, int, bool, List, Dict, None] = None) -> List:
|
||||
r"""Return nodes matching `attr` and `value`
|
||||
|
||||
Arguments:
|
||||
attr: Name of Blender property
|
||||
value: Value of attribute. If none
|
||||
is provided, return all nodes with this attribute.
|
||||
|
||||
Example:
|
||||
>>> lsattr("id", "myId")
|
||||
... [bpy.data.objects["myNode"]
|
||||
>>> lsattr("id")
|
||||
... [bpy.data.objects["myNode"], bpy.data.objects["myOtherNode"]]
|
||||
|
||||
Returns:
|
||||
list
|
||||
"""
|
||||
|
||||
return lsattrs({attr: value})
|
||||
|
||||
|
||||
def lsattrs(attrs: Dict) -> List:
|
||||
r"""Return nodes with the given attribute(s).
|
||||
|
||||
Arguments:
|
||||
attrs: Name and value pairs of expected matches
|
||||
|
||||
Example:
|
||||
>>> lsattrs({"age": 5}) # Return nodes with an `age` of 5
|
||||
# Return nodes with both `age` and `color` of 5 and blue
|
||||
>>> lsattrs({"age": 5, "color": "blue"})
|
||||
|
||||
Returns a list.
|
||||
|
||||
"""
|
||||
|
||||
# For now return all objects, not filtered by scene/collection/view_layer.
|
||||
matches = set()
|
||||
for coll in dir(bpy.data):
|
||||
if not isinstance(
|
||||
getattr(bpy.data, coll),
|
||||
bpy.types.bpy_prop_collection,
|
||||
):
|
||||
continue
|
||||
for node in getattr(bpy.data, coll):
|
||||
for attr, value in attrs.items():
|
||||
avalon_prop = node.get(pipeline.AVALON_PROPERTY)
|
||||
if not avalon_prop:
|
||||
continue
|
||||
if (avalon_prop.get(attr)
|
||||
and (value is None or avalon_prop.get(attr) == value)):
|
||||
matches.add(node)
|
||||
return list(matches)
|
||||
|
||||
|
||||
def read(node: bpy.types.bpy_struct_meta_idprop):
|
||||
"""Return user-defined attributes from `node`"""
|
||||
|
||||
data = dict(node.get(pipeline.AVALON_PROPERTY))
|
||||
|
||||
# Ignore hidden/internal data
|
||||
data = {
|
||||
key: value
|
||||
for key, value in data.items() if not key.startswith("_")
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def get_selection() -> List[bpy.types.Object]:
|
||||
"""Return the selected objects from the current scene."""
|
||||
return [obj for obj in bpy.context.scene.objects if obj.select_get()]
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def maintained_selection():
|
||||
r"""Maintain selection during context
|
||||
|
||||
Example:
|
||||
>>> with maintained_selection():
|
||||
... # Modify selection
|
||||
... bpy.ops.object.select_all(action='DESELECT')
|
||||
>>> # Selection restored
|
||||
"""
|
||||
|
||||
previous_selection = get_selection()
|
||||
previous_active = bpy.context.view_layer.objects.active
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
# Clear the selection
|
||||
for node in get_selection():
|
||||
node.select_set(state=False)
|
||||
if previous_selection:
|
||||
for node in previous_selection:
|
||||
try:
|
||||
node.select_set(state=True)
|
||||
except ReferenceError:
|
||||
# This could happen if a selected node was deleted during
|
||||
# the context.
|
||||
log.exception("Failed to reselect")
|
||||
continue
|
||||
try:
|
||||
bpy.context.view_layer.objects.active = previous_active
|
||||
except ReferenceError:
|
||||
# This could happen if the active node was deleted during the
|
||||
# context.
|
||||
log.exception("Failed to set active object.")
|
||||
|
|
|
|||
410
openpype/hosts/blender/api/ops.py
Normal file
410
openpype/hosts/blender/api/ops.py
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
"""Blender operators and menus for use with Avalon."""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import platform
|
||||
import time
|
||||
import traceback
|
||||
import collections
|
||||
from pathlib import Path
|
||||
from types import ModuleType
|
||||
from typing import Dict, List, Optional, Union
|
||||
|
||||
from Qt import QtWidgets, QtCore
|
||||
|
||||
import bpy
|
||||
import bpy.utils.previews
|
||||
|
||||
import avalon.api
|
||||
from openpype.tools.utils import host_tools
|
||||
from openpype import style
|
||||
|
||||
from .workio import OpenFileCacher
|
||||
|
||||
PREVIEW_COLLECTIONS: Dict = dict()
|
||||
|
||||
# This seems like a good value to keep the Qt app responsive and doesn't slow
|
||||
# down Blender. At least on macOS I the interace of Blender gets very laggy if
|
||||
# you make it smaller.
|
||||
TIMER_INTERVAL: float = 0.01
|
||||
|
||||
|
||||
class BlenderApplication(QtWidgets.QApplication):
|
||||
_instance = None
|
||||
blender_windows = {}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(BlenderApplication, self).__init__(*args, **kwargs)
|
||||
self.setQuitOnLastWindowClosed(False)
|
||||
|
||||
self.setStyleSheet(style.load_stylesheet())
|
||||
self.lastWindowClosed.connect(self.__class__.reset)
|
||||
|
||||
@classmethod
|
||||
def get_app(cls):
|
||||
if cls._instance is None:
|
||||
cls._instance = cls(sys.argv)
|
||||
return cls._instance
|
||||
|
||||
@classmethod
|
||||
def reset(cls):
|
||||
cls._instance = None
|
||||
|
||||
@classmethod
|
||||
def store_window(cls, identifier, window):
|
||||
current_window = cls.get_window(identifier)
|
||||
cls.blender_windows[identifier] = window
|
||||
if current_window:
|
||||
current_window.close()
|
||||
# current_window.deleteLater()
|
||||
|
||||
@classmethod
|
||||
def get_window(cls, identifier):
|
||||
return cls.blender_windows.get(identifier)
|
||||
|
||||
|
||||
class MainThreadItem:
|
||||
"""Structure to store information about callback in main thread.
|
||||
|
||||
Item should be used to execute callback in main thread which may be needed
|
||||
for execution of Qt objects.
|
||||
|
||||
Item store callback (callable variable), arguments and keyword arguments
|
||||
for the callback. Item hold information about it's process.
|
||||
"""
|
||||
not_set = object()
|
||||
sleep_time = 0.1
|
||||
|
||||
def __init__(self, callback, *args, **kwargs):
|
||||
self.done = False
|
||||
self.exception = self.not_set
|
||||
self.result = self.not_set
|
||||
self.callback = callback
|
||||
self.args = args
|
||||
self.kwargs = kwargs
|
||||
|
||||
def execute(self):
|
||||
"""Execute callback and store it's result.
|
||||
|
||||
Method must be called from main thread. Item is marked as `done`
|
||||
when callback execution finished. Store output of callback of exception
|
||||
information when callback raise one.
|
||||
"""
|
||||
print("Executing process in main thread")
|
||||
if self.done:
|
||||
print("- item is already processed")
|
||||
return
|
||||
|
||||
callback = self.callback
|
||||
args = self.args
|
||||
kwargs = self.kwargs
|
||||
print("Running callback: {}".format(str(callback)))
|
||||
try:
|
||||
result = callback(*args, **kwargs)
|
||||
self.result = result
|
||||
|
||||
except Exception:
|
||||
self.exception = sys.exc_info()
|
||||
|
||||
finally:
|
||||
print("Done")
|
||||
self.done = True
|
||||
|
||||
def wait(self):
|
||||
"""Wait for result from main thread.
|
||||
|
||||
This method stops current thread until callback is executed.
|
||||
|
||||
Returns:
|
||||
object: Output of callback. May be any type or object.
|
||||
|
||||
Raises:
|
||||
Exception: Reraise any exception that happened during callback
|
||||
execution.
|
||||
"""
|
||||
while not self.done:
|
||||
print(self.done)
|
||||
time.sleep(self.sleep_time)
|
||||
|
||||
if self.exception is self.not_set:
|
||||
return self.result
|
||||
raise self.exception
|
||||
|
||||
|
||||
class GlobalClass:
|
||||
app = None
|
||||
main_thread_callbacks = collections.deque()
|
||||
is_windows = platform.system().lower() == "windows"
|
||||
|
||||
|
||||
def execute_in_main_thread(main_thead_item):
|
||||
print("execute_in_main_thread")
|
||||
GlobalClass.main_thread_callbacks.append(main_thead_item)
|
||||
|
||||
|
||||
def _process_app_events() -> Optional[float]:
|
||||
"""Process the events of the Qt app if the window is still visible.
|
||||
|
||||
If the app has any top level windows and at least one of them is visible
|
||||
return the time after which this function should be run again. Else return
|
||||
None, so the function is not run again and will be unregistered.
|
||||
"""
|
||||
while GlobalClass.main_thread_callbacks:
|
||||
main_thread_item = GlobalClass.main_thread_callbacks.popleft()
|
||||
main_thread_item.execute()
|
||||
if main_thread_item.exception is not MainThreadItem.not_set:
|
||||
_clc, val, tb = main_thread_item.exception
|
||||
msg = str(val)
|
||||
detail = "\n".join(traceback.format_exception(_clc, val, tb))
|
||||
dialog = QtWidgets.QMessageBox(
|
||||
QtWidgets.QMessageBox.Warning,
|
||||
"Error",
|
||||
msg)
|
||||
dialog.setMinimumWidth(500)
|
||||
dialog.setDetailedText(detail)
|
||||
dialog.exec_()
|
||||
|
||||
if not GlobalClass.is_windows:
|
||||
if OpenFileCacher.opening_file:
|
||||
return TIMER_INTERVAL
|
||||
|
||||
app = GlobalClass.app
|
||||
if app._instance:
|
||||
app.processEvents()
|
||||
return TIMER_INTERVAL
|
||||
return TIMER_INTERVAL
|
||||
|
||||
|
||||
class LaunchQtApp(bpy.types.Operator):
|
||||
"""A Base class for opertors to launch a Qt app."""
|
||||
|
||||
_app: QtWidgets.QApplication
|
||||
_window = Union[QtWidgets.QDialog, ModuleType]
|
||||
_tool_name: str = None
|
||||
_init_args: Optional[List] = list()
|
||||
_init_kwargs: Optional[Dict] = dict()
|
||||
bl_idname: str = None
|
||||
|
||||
def __init__(self):
|
||||
if self.bl_idname is None:
|
||||
raise NotImplementedError("Attribute `bl_idname` must be set!")
|
||||
print(f"Initialising {self.bl_idname}...")
|
||||
self._app = BlenderApplication.get_app()
|
||||
GlobalClass.app = self._app
|
||||
|
||||
bpy.app.timers.register(
|
||||
_process_app_events,
|
||||
persistent=True
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
"""Execute the operator.
|
||||
|
||||
The child class must implement `execute()` where it only has to set
|
||||
`self._window` to the desired Qt window and then simply run
|
||||
`return super().execute(context)`.
|
||||
`self._window` is expected to have a `show` method.
|
||||
If the `show` method requires arguments, you can set `self._show_args`
|
||||
and `self._show_kwargs`. `args` should be a list, `kwargs` a
|
||||
dictionary.
|
||||
"""
|
||||
|
||||
if self._tool_name is None:
|
||||
if self._window is None:
|
||||
raise AttributeError("`self._window` is not set.")
|
||||
|
||||
else:
|
||||
window = self._app.get_window(self.bl_idname)
|
||||
if window is None:
|
||||
window = host_tools.get_tool_by_name(self._tool_name)
|
||||
self._app.store_window(self.bl_idname, window)
|
||||
self._window = window
|
||||
|
||||
if not isinstance(
|
||||
self._window,
|
||||
(QtWidgets.QMainWindow, QtWidgets.QDialog, ModuleType)
|
||||
):
|
||||
raise AttributeError(
|
||||
"`window` should be a `QDialog or module`. Got: {}".format(
|
||||
str(type(window))
|
||||
)
|
||||
)
|
||||
|
||||
self.before_window_show()
|
||||
|
||||
if isinstance(self._window, ModuleType):
|
||||
self._window.show()
|
||||
window = None
|
||||
if hasattr(self._window, "window"):
|
||||
window = self._window.window
|
||||
elif hasattr(self._window, "_window"):
|
||||
window = self._window.window
|
||||
|
||||
if window:
|
||||
self._app.store_window(self.bl_idname, window)
|
||||
|
||||
else:
|
||||
origin_flags = self._window.windowFlags()
|
||||
on_top_flags = origin_flags | QtCore.Qt.WindowStaysOnTopHint
|
||||
self._window.setWindowFlags(on_top_flags)
|
||||
self._window.show()
|
||||
|
||||
if on_top_flags != origin_flags:
|
||||
self._window.setWindowFlags(origin_flags)
|
||||
self._window.show()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def before_window_show(self):
|
||||
return
|
||||
|
||||
|
||||
class LaunchCreator(LaunchQtApp):
|
||||
"""Launch Avalon Creator."""
|
||||
|
||||
bl_idname = "wm.avalon_creator"
|
||||
bl_label = "Create..."
|
||||
_tool_name = "creator"
|
||||
|
||||
def before_window_show(self):
|
||||
self._window.refresh()
|
||||
|
||||
|
||||
class LaunchLoader(LaunchQtApp):
|
||||
"""Launch Avalon Loader."""
|
||||
|
||||
bl_idname = "wm.avalon_loader"
|
||||
bl_label = "Load..."
|
||||
_tool_name = "loader"
|
||||
|
||||
def before_window_show(self):
|
||||
self._window.set_context(
|
||||
{"asset": avalon.api.Session["AVALON_ASSET"]},
|
||||
refresh=True
|
||||
)
|
||||
|
||||
|
||||
class LaunchPublisher(LaunchQtApp):
|
||||
"""Launch Avalon Publisher."""
|
||||
|
||||
bl_idname = "wm.avalon_publisher"
|
||||
bl_label = "Publish..."
|
||||
|
||||
def execute(self, context):
|
||||
host_tools.show_publish()
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
class LaunchManager(LaunchQtApp):
|
||||
"""Launch Avalon Manager."""
|
||||
|
||||
bl_idname = "wm.avalon_manager"
|
||||
bl_label = "Manage..."
|
||||
_tool_name = "sceneinventory"
|
||||
|
||||
def before_window_show(self):
|
||||
self._window.refresh()
|
||||
|
||||
|
||||
class LaunchWorkFiles(LaunchQtApp):
|
||||
"""Launch Avalon Work Files."""
|
||||
|
||||
bl_idname = "wm.avalon_workfiles"
|
||||
bl_label = "Work Files..."
|
||||
_tool_name = "workfiles"
|
||||
|
||||
def execute(self, context):
|
||||
result = super().execute(context)
|
||||
self._window.set_context({
|
||||
"asset": avalon.api.Session["AVALON_ASSET"],
|
||||
"silo": avalon.api.Session["AVALON_SILO"],
|
||||
"task": avalon.api.Session["AVALON_TASK"]
|
||||
})
|
||||
return result
|
||||
|
||||
def before_window_show(self):
|
||||
self._window.root = str(Path(
|
||||
os.environ.get("AVALON_WORKDIR", ""),
|
||||
os.environ.get("AVALON_SCENEDIR", ""),
|
||||
))
|
||||
self._window.refresh()
|
||||
|
||||
|
||||
class TOPBAR_MT_avalon(bpy.types.Menu):
|
||||
"""Avalon menu."""
|
||||
|
||||
bl_idname = "TOPBAR_MT_avalon"
|
||||
bl_label = os.environ.get("AVALON_LABEL")
|
||||
|
||||
def draw(self, context):
|
||||
"""Draw the menu in the UI."""
|
||||
|
||||
layout = self.layout
|
||||
|
||||
pcoll = PREVIEW_COLLECTIONS.get("avalon")
|
||||
if pcoll:
|
||||
pyblish_menu_icon = pcoll["pyblish_menu_icon"]
|
||||
pyblish_menu_icon_id = pyblish_menu_icon.icon_id
|
||||
else:
|
||||
pyblish_menu_icon_id = 0
|
||||
|
||||
asset = avalon.api.Session['AVALON_ASSET']
|
||||
task = avalon.api.Session['AVALON_TASK']
|
||||
context_label = f"{asset}, {task}"
|
||||
context_label_item = layout.row()
|
||||
context_label_item.operator(
|
||||
LaunchWorkFiles.bl_idname, text=context_label
|
||||
)
|
||||
context_label_item.enabled = False
|
||||
layout.separator()
|
||||
layout.operator(LaunchCreator.bl_idname, text="Create...")
|
||||
layout.operator(LaunchLoader.bl_idname, text="Load...")
|
||||
layout.operator(
|
||||
LaunchPublisher.bl_idname,
|
||||
text="Publish...",
|
||||
icon_value=pyblish_menu_icon_id,
|
||||
)
|
||||
layout.operator(LaunchManager.bl_idname, text="Manage...")
|
||||
layout.separator()
|
||||
layout.operator(LaunchWorkFiles.bl_idname, text="Work Files...")
|
||||
# TODO (jasper): maybe add 'Reload Pipeline', 'Reset Frame Range' and
|
||||
# 'Reset Resolution'?
|
||||
|
||||
|
||||
def draw_avalon_menu(self, context):
|
||||
"""Draw the Avalon menu in the top bar."""
|
||||
|
||||
self.layout.menu(TOPBAR_MT_avalon.bl_idname)
|
||||
|
||||
|
||||
classes = [
|
||||
LaunchCreator,
|
||||
LaunchLoader,
|
||||
LaunchPublisher,
|
||||
LaunchManager,
|
||||
LaunchWorkFiles,
|
||||
TOPBAR_MT_avalon,
|
||||
]
|
||||
|
||||
|
||||
def register():
|
||||
"Register the operators and menu."
|
||||
|
||||
pcoll = bpy.utils.previews.new()
|
||||
pyblish_icon_file = Path(__file__).parent / "icons" / "pyblish-32x32.png"
|
||||
pcoll.load("pyblish_menu_icon", str(pyblish_icon_file.absolute()), 'IMAGE')
|
||||
PREVIEW_COLLECTIONS["avalon"] = pcoll
|
||||
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
bpy.types.TOPBAR_MT_editor_menus.append(draw_avalon_menu)
|
||||
|
||||
|
||||
def unregister():
|
||||
"""Unregister the operators and menu."""
|
||||
|
||||
pcoll = PREVIEW_COLLECTIONS.pop("avalon")
|
||||
bpy.utils.previews.remove(pcoll)
|
||||
bpy.types.TOPBAR_MT_editor_menus.remove(draw_avalon_menu)
|
||||
for cls in reversed(classes):
|
||||
bpy.utils.unregister_class(cls)
|
||||
427
openpype/hosts/blender/api/pipeline.py
Normal file
427
openpype/hosts/blender/api/pipeline.py
Normal file
|
|
@ -0,0 +1,427 @@
|
|||
import os
|
||||
import sys
|
||||
import importlib
|
||||
import traceback
|
||||
from typing import Callable, Dict, Iterator, List, Optional
|
||||
|
||||
import bpy
|
||||
|
||||
from . import lib
|
||||
from . import ops
|
||||
|
||||
import pyblish.api
|
||||
import avalon.api
|
||||
from avalon import io, schema
|
||||
from avalon.pipeline import AVALON_CONTAINER_ID
|
||||
|
||||
from openpype.api import Logger
|
||||
import openpype.hosts.blender
|
||||
|
||||
HOST_DIR = os.path.dirname(os.path.abspath(openpype.hosts.blender.__file__))
|
||||
PLUGINS_DIR = os.path.join(HOST_DIR, "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")
|
||||
INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")
|
||||
|
||||
ORIGINAL_EXCEPTHOOK = sys.excepthook
|
||||
|
||||
AVALON_INSTANCES = "AVALON_INSTANCES"
|
||||
AVALON_CONTAINERS = "AVALON_CONTAINERS"
|
||||
AVALON_PROPERTY = 'avalon'
|
||||
IS_HEADLESS = bpy.app.background
|
||||
|
||||
log = Logger.get_logger(__name__)
|
||||
|
||||
|
||||
def pype_excepthook_handler(*args):
|
||||
traceback.print_exception(*args)
|
||||
|
||||
|
||||
def install():
|
||||
"""Install Blender configuration for Avalon."""
|
||||
sys.excepthook = pype_excepthook_handler
|
||||
|
||||
pyblish.api.register_host("blender")
|
||||
pyblish.api.register_plugin_path(str(PUBLISH_PATH))
|
||||
|
||||
avalon.api.register_plugin_path(avalon.api.Loader, str(LOAD_PATH))
|
||||
avalon.api.register_plugin_path(avalon.api.Creator, str(CREATE_PATH))
|
||||
|
||||
lib.append_user_scripts()
|
||||
|
||||
avalon.api.on("new", on_new)
|
||||
avalon.api.on("open", on_open)
|
||||
_register_callbacks()
|
||||
_register_events()
|
||||
|
||||
if not IS_HEADLESS:
|
||||
ops.register()
|
||||
|
||||
|
||||
def uninstall():
|
||||
"""Uninstall Blender configuration for Avalon."""
|
||||
sys.excepthook = ORIGINAL_EXCEPTHOOK
|
||||
|
||||
pyblish.api.deregister_host("blender")
|
||||
pyblish.api.deregister_plugin_path(str(PUBLISH_PATH))
|
||||
|
||||
avalon.api.deregister_plugin_path(avalon.api.Loader, str(LOAD_PATH))
|
||||
avalon.api.deregister_plugin_path(avalon.api.Creator, str(CREATE_PATH))
|
||||
|
||||
if not IS_HEADLESS:
|
||||
ops.unregister()
|
||||
|
||||
|
||||
def set_start_end_frames():
|
||||
asset_name = io.Session["AVALON_ASSET"]
|
||||
asset_doc = io.find_one({
|
||||
"type": "asset",
|
||||
"name": asset_name
|
||||
})
|
||||
|
||||
scene = bpy.context.scene
|
||||
|
||||
# Default scene settings
|
||||
frameStart = scene.frame_start
|
||||
frameEnd = scene.frame_end
|
||||
fps = scene.render.fps
|
||||
resolution_x = scene.render.resolution_x
|
||||
resolution_y = scene.render.resolution_y
|
||||
|
||||
# Check if settings are set
|
||||
data = asset_doc.get("data")
|
||||
|
||||
if not data:
|
||||
return
|
||||
|
||||
if data.get("frameStart"):
|
||||
frameStart = data.get("frameStart")
|
||||
if data.get("frameEnd"):
|
||||
frameEnd = data.get("frameEnd")
|
||||
if data.get("fps"):
|
||||
fps = data.get("fps")
|
||||
if data.get("resolutionWidth"):
|
||||
resolution_x = data.get("resolutionWidth")
|
||||
if data.get("resolutionHeight"):
|
||||
resolution_y = data.get("resolutionHeight")
|
||||
|
||||
scene.frame_start = frameStart
|
||||
scene.frame_end = frameEnd
|
||||
scene.render.fps = fps
|
||||
scene.render.resolution_x = resolution_x
|
||||
scene.render.resolution_y = resolution_y
|
||||
|
||||
|
||||
def on_new(arg1, arg2):
|
||||
set_start_end_frames()
|
||||
|
||||
|
||||
def on_open(arg1, arg2):
|
||||
set_start_end_frames()
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def _on_save_pre(*args):
|
||||
avalon.api.emit("before_save", args)
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def _on_save_post(*args):
|
||||
avalon.api.emit("save", args)
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def _on_load_post(*args):
|
||||
# Detect new file or opening an existing file
|
||||
if bpy.data.filepath:
|
||||
# Likely this was an open operation since it has a filepath
|
||||
avalon.api.emit("open", args)
|
||||
else:
|
||||
avalon.api.emit("new", args)
|
||||
|
||||
ops.OpenFileCacher.post_load()
|
||||
|
||||
|
||||
def _register_callbacks():
|
||||
"""Register callbacks for certain events."""
|
||||
def _remove_handler(handlers: List, callback: Callable):
|
||||
"""Remove the callback from the given handler list."""
|
||||
|
||||
try:
|
||||
handlers.remove(callback)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# TODO (jasper): implement on_init callback?
|
||||
|
||||
# Be sure to remove existig ones first.
|
||||
_remove_handler(bpy.app.handlers.save_pre, _on_save_pre)
|
||||
_remove_handler(bpy.app.handlers.save_post, _on_save_post)
|
||||
_remove_handler(bpy.app.handlers.load_post, _on_load_post)
|
||||
|
||||
bpy.app.handlers.save_pre.append(_on_save_pre)
|
||||
bpy.app.handlers.save_post.append(_on_save_post)
|
||||
bpy.app.handlers.load_post.append(_on_load_post)
|
||||
|
||||
log.info("Installed event handler _on_save_pre...")
|
||||
log.info("Installed event handler _on_save_post...")
|
||||
log.info("Installed event handler _on_load_post...")
|
||||
|
||||
|
||||
def _on_task_changed(*args):
|
||||
"""Callback for when the task in the context is changed."""
|
||||
|
||||
# TODO (jasper): Blender has no concept of projects or workspace.
|
||||
# It would be nice to override 'bpy.ops.wm.open_mainfile' so it takes the
|
||||
# workdir as starting directory. But I don't know if that is possible.
|
||||
# Another option would be to create a custom 'File Selector' and add the
|
||||
# `directory` attribute, so it opens in that directory (does it?).
|
||||
# https://docs.blender.org/api/blender2.8/bpy.types.Operator.html#calling-a-file-selector
|
||||
# https://docs.blender.org/api/blender2.8/bpy.types.WindowManager.html#bpy.types.WindowManager.fileselect_add
|
||||
workdir = avalon.api.Session["AVALON_WORKDIR"]
|
||||
log.debug("New working directory: %s", workdir)
|
||||
|
||||
|
||||
def _register_events():
|
||||
"""Install callbacks for specific events."""
|
||||
|
||||
avalon.api.on("taskChanged", _on_task_changed)
|
||||
log.info("Installed event callback for 'taskChanged'...")
|
||||
|
||||
|
||||
def reload_pipeline(*args):
|
||||
"""Attempt to reload pipeline at run-time.
|
||||
|
||||
Warning:
|
||||
This is primarily for development and debugging purposes and not well
|
||||
tested.
|
||||
|
||||
"""
|
||||
|
||||
avalon.api.uninstall()
|
||||
|
||||
for module in (
|
||||
"avalon.io",
|
||||
"avalon.lib",
|
||||
"avalon.pipeline",
|
||||
"avalon.tools.creator.app",
|
||||
"avalon.tools.manager.app",
|
||||
"avalon.api",
|
||||
"avalon.tools",
|
||||
):
|
||||
module = importlib.import_module(module)
|
||||
importlib.reload(module)
|
||||
|
||||
|
||||
def _discover_gui() -> Optional[Callable]:
|
||||
"""Return the most desirable of the currently registered GUIs"""
|
||||
|
||||
# Prefer last registered
|
||||
guis = reversed(pyblish.api.registered_guis())
|
||||
|
||||
for gui in guis:
|
||||
try:
|
||||
gui = __import__(gui).show
|
||||
except (ImportError, AttributeError):
|
||||
continue
|
||||
else:
|
||||
return gui
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def add_to_avalon_container(container: bpy.types.Collection):
|
||||
"""Add the container to the Avalon container."""
|
||||
|
||||
avalon_container = bpy.data.collections.get(AVALON_CONTAINERS)
|
||||
if not avalon_container:
|
||||
avalon_container = bpy.data.collections.new(name=AVALON_CONTAINERS)
|
||||
|
||||
# Link the container to the scene so it's easily visible to the artist
|
||||
# and can be managed easily. Otherwise it's only found in "Blender
|
||||
# File" view and it will be removed by Blenders garbage collection,
|
||||
# unless you set a 'fake user'.
|
||||
bpy.context.scene.collection.children.link(avalon_container)
|
||||
|
||||
avalon_container.children.link(container)
|
||||
|
||||
# Disable Avalon containers for the view layers.
|
||||
for view_layer in bpy.context.scene.view_layers:
|
||||
for child in view_layer.layer_collection.children:
|
||||
if child.collection == avalon_container:
|
||||
child.exclude = True
|
||||
|
||||
|
||||
def metadata_update(node: bpy.types.bpy_struct_meta_idprop, data: Dict):
|
||||
"""Imprint the node with metadata.
|
||||
|
||||
Existing metadata will be updated.
|
||||
"""
|
||||
|
||||
if not node.get(AVALON_PROPERTY):
|
||||
node[AVALON_PROPERTY] = dict()
|
||||
for key, value in data.items():
|
||||
if value is None:
|
||||
continue
|
||||
node[AVALON_PROPERTY][key] = value
|
||||
|
||||
|
||||
def containerise(name: str,
|
||||
namespace: str,
|
||||
nodes: List,
|
||||
context: Dict,
|
||||
loader: Optional[str] = None,
|
||||
suffix: Optional[str] = "CON") -> bpy.types.Collection:
|
||||
"""Bundle `nodes` into an assembly and imprint it with metadata
|
||||
|
||||
Containerisation enables a tracking of version, author and origin
|
||||
for loaded assets.
|
||||
|
||||
Arguments:
|
||||
name: Name of resulting assembly
|
||||
namespace: Namespace under which to host container
|
||||
nodes: Long names of nodes to containerise
|
||||
context: Asset information
|
||||
loader: Name of loader used to produce this container.
|
||||
suffix: Suffix of container, defaults to `_CON`.
|
||||
|
||||
Returns:
|
||||
The container assembly
|
||||
|
||||
"""
|
||||
|
||||
node_name = f"{context['asset']['name']}_{name}"
|
||||
if namespace:
|
||||
node_name = f"{namespace}:{node_name}"
|
||||
if suffix:
|
||||
node_name = f"{node_name}_{suffix}"
|
||||
container = bpy.data.collections.new(name=node_name)
|
||||
# Link the children nodes
|
||||
for obj in nodes:
|
||||
container.objects.link(obj)
|
||||
|
||||
data = {
|
||||
"schema": "openpype:container-2.0",
|
||||
"id": AVALON_CONTAINER_ID,
|
||||
"name": name,
|
||||
"namespace": namespace or '',
|
||||
"loader": str(loader),
|
||||
"representation": str(context["representation"]["_id"]),
|
||||
}
|
||||
|
||||
metadata_update(container, data)
|
||||
add_to_avalon_container(container)
|
||||
|
||||
return container
|
||||
|
||||
|
||||
def containerise_existing(
|
||||
container: bpy.types.Collection,
|
||||
name: str,
|
||||
namespace: str,
|
||||
context: Dict,
|
||||
loader: Optional[str] = None,
|
||||
suffix: Optional[str] = "CON") -> bpy.types.Collection:
|
||||
"""Imprint or update container with metadata.
|
||||
|
||||
Arguments:
|
||||
name: Name of resulting assembly
|
||||
namespace: Namespace under which to host container
|
||||
context: Asset information
|
||||
loader: Name of loader used to produce this container.
|
||||
suffix: Suffix of container, defaults to `_CON`.
|
||||
|
||||
Returns:
|
||||
The container assembly
|
||||
"""
|
||||
|
||||
node_name = container.name
|
||||
if suffix:
|
||||
node_name = f"{node_name}_{suffix}"
|
||||
container.name = node_name
|
||||
data = {
|
||||
"schema": "openpype:container-2.0",
|
||||
"id": AVALON_CONTAINER_ID,
|
||||
"name": name,
|
||||
"namespace": namespace or '',
|
||||
"loader": str(loader),
|
||||
"representation": str(context["representation"]["_id"]),
|
||||
}
|
||||
|
||||
metadata_update(container, data)
|
||||
add_to_avalon_container(container)
|
||||
|
||||
return container
|
||||
|
||||
|
||||
def parse_container(container: bpy.types.Collection,
|
||||
validate: bool = True) -> Dict:
|
||||
"""Return the container node's full container data.
|
||||
|
||||
Args:
|
||||
container: A container node name.
|
||||
validate: turn the validation for the container on or off
|
||||
|
||||
Returns:
|
||||
The container schema data for this container node.
|
||||
|
||||
"""
|
||||
|
||||
data = lib.read(container)
|
||||
|
||||
# Append transient data
|
||||
data["objectName"] = container.name
|
||||
|
||||
if validate:
|
||||
schema.validate(data)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def ls() -> Iterator:
|
||||
"""List containers from active Blender scene.
|
||||
|
||||
This is the host-equivalent of api.ls(), but instead of listing assets on
|
||||
disk, it lists assets already loaded in Blender; once loaded they are
|
||||
called containers.
|
||||
"""
|
||||
|
||||
for container in lib.lsattr("id", AVALON_CONTAINER_ID):
|
||||
yield parse_container(container)
|
||||
|
||||
|
||||
def update_hierarchy(containers):
|
||||
"""Hierarchical container support
|
||||
|
||||
This is the function to support Scene Inventory to draw hierarchical
|
||||
view for containers.
|
||||
|
||||
We need both parent and children to visualize the graph.
|
||||
|
||||
"""
|
||||
|
||||
all_containers = set(ls()) # lookup set
|
||||
|
||||
for container in containers:
|
||||
# Find parent
|
||||
# FIXME (jasperge): re-evaluate this. How would it be possible
|
||||
# to 'nest' assets? Collections can have several parents, for
|
||||
# now assume it has only 1 parent
|
||||
parent = [
|
||||
coll for coll in bpy.data.collections if container in coll.children
|
||||
]
|
||||
for node in parent:
|
||||
if node in all_containers:
|
||||
container["parent"] = node
|
||||
break
|
||||
|
||||
log.debug("Container: %s", container)
|
||||
|
||||
yield container
|
||||
|
||||
|
||||
def publish():
|
||||
"""Shorthand to publish from within host."""
|
||||
|
||||
return pyblish.util.publish()
|
||||
|
|
@ -5,10 +5,17 @@ from typing import Dict, List, Optional
|
|||
|
||||
import bpy
|
||||
|
||||
from avalon import api, blender
|
||||
from avalon.blender import ops
|
||||
from avalon.blender.pipeline import AVALON_CONTAINERS
|
||||
import avalon.api
|
||||
from openpype.api import PypeCreatorMixin
|
||||
from .pipeline import AVALON_CONTAINERS
|
||||
from .ops import (
|
||||
MainThreadItem,
|
||||
execute_in_main_thread
|
||||
)
|
||||
from .lib import (
|
||||
imprint,
|
||||
get_selection
|
||||
)
|
||||
|
||||
VALID_EXTENSIONS = [".blend", ".json", ".abc", ".fbx"]
|
||||
|
||||
|
|
@ -119,11 +126,27 @@ def deselect_all():
|
|||
bpy.context.view_layer.objects.active = active
|
||||
|
||||
|
||||
class Creator(PypeCreatorMixin, blender.Creator):
|
||||
pass
|
||||
class Creator(PypeCreatorMixin, avalon.api.Creator):
|
||||
"""Base class for Creator plug-ins."""
|
||||
def process(self):
|
||||
collection = bpy.data.collections.new(name=self.data["subset"])
|
||||
bpy.context.scene.collection.children.link(collection)
|
||||
imprint(collection, self.data)
|
||||
|
||||
if (self.options or {}).get("useSelection"):
|
||||
for obj in get_selection():
|
||||
collection.objects.link(obj)
|
||||
|
||||
return collection
|
||||
|
||||
|
||||
class AssetLoader(api.Loader):
|
||||
class Loader(avalon.api.Loader):
|
||||
"""Base class for Loader plug-ins."""
|
||||
|
||||
hosts = ["blender"]
|
||||
|
||||
|
||||
class AssetLoader(avalon.api.Loader):
|
||||
"""A basic AssetLoader for Blender
|
||||
|
||||
This will implement the basic logic for linking/appending assets
|
||||
|
|
@ -191,8 +214,8 @@ class AssetLoader(api.Loader):
|
|||
namespace: Optional[str] = None,
|
||||
options: Optional[Dict] = None) -> Optional[bpy.types.Collection]:
|
||||
""" Run the loader on Blender main thread"""
|
||||
mti = ops.MainThreadItem(self._load, context, name, namespace, options)
|
||||
ops.execute_in_main_thread(mti)
|
||||
mti = MainThreadItem(self._load, context, name, namespace, options)
|
||||
execute_in_main_thread(mti)
|
||||
|
||||
def _load(self,
|
||||
context: dict,
|
||||
|
|
@ -257,8 +280,8 @@ class AssetLoader(api.Loader):
|
|||
|
||||
def update(self, container: Dict, representation: Dict):
|
||||
""" Run the update on Blender main thread"""
|
||||
mti = ops.MainThreadItem(self.exec_update, container, representation)
|
||||
ops.execute_in_main_thread(mti)
|
||||
mti = MainThreadItem(self.exec_update, container, representation)
|
||||
execute_in_main_thread(mti)
|
||||
|
||||
def exec_remove(self, container: Dict) -> bool:
|
||||
"""Must be implemented by a sub-class"""
|
||||
|
|
@ -266,5 +289,5 @@ class AssetLoader(api.Loader):
|
|||
|
||||
def remove(self, container: Dict) -> bool:
|
||||
""" Run the remove on Blender main thread"""
|
||||
mti = ops.MainThreadItem(self.exec_remove, container)
|
||||
ops.execute_in_main_thread(mti)
|
||||
mti = MainThreadItem(self.exec_remove, container)
|
||||
execute_in_main_thread(mti)
|
||||
|
|
|
|||
90
openpype/hosts/blender/api/workio.py
Normal file
90
openpype/hosts/blender/api/workio.py
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
"""Host API required for Work Files."""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import List, Optional
|
||||
|
||||
import bpy
|
||||
from avalon import api
|
||||
|
||||
|
||||
class OpenFileCacher:
|
||||
"""Store information about opening file.
|
||||
|
||||
When file is opening QApplcation events should not be processed.
|
||||
"""
|
||||
opening_file = False
|
||||
|
||||
@classmethod
|
||||
def post_load(cls):
|
||||
cls.opening_file = False
|
||||
|
||||
@classmethod
|
||||
def set_opening(cls):
|
||||
cls.opening_file = True
|
||||
|
||||
|
||||
def open_file(filepath: str) -> Optional[str]:
|
||||
"""Open the scene file in Blender."""
|
||||
OpenFileCacher.set_opening()
|
||||
|
||||
preferences = bpy.context.preferences
|
||||
load_ui = preferences.filepaths.use_load_ui
|
||||
use_scripts = preferences.filepaths.use_scripts_auto_execute
|
||||
result = bpy.ops.wm.open_mainfile(
|
||||
filepath=filepath,
|
||||
load_ui=load_ui,
|
||||
use_scripts=use_scripts,
|
||||
)
|
||||
|
||||
if result == {'FINISHED'}:
|
||||
return filepath
|
||||
return None
|
||||
|
||||
|
||||
def save_file(filepath: str, copy: bool = False) -> Optional[str]:
|
||||
"""Save the open scene file."""
|
||||
|
||||
preferences = bpy.context.preferences
|
||||
compress = preferences.filepaths.use_file_compression
|
||||
relative_remap = preferences.filepaths.use_relative_paths
|
||||
result = bpy.ops.wm.save_as_mainfile(
|
||||
filepath=filepath,
|
||||
compress=compress,
|
||||
relative_remap=relative_remap,
|
||||
copy=copy,
|
||||
)
|
||||
|
||||
if result == {'FINISHED'}:
|
||||
return filepath
|
||||
return None
|
||||
|
||||
|
||||
def current_file() -> Optional[str]:
|
||||
"""Return the path of the open scene file."""
|
||||
|
||||
current_filepath = bpy.data.filepath
|
||||
if Path(current_filepath).is_file():
|
||||
return current_filepath
|
||||
return None
|
||||
|
||||
|
||||
def has_unsaved_changes() -> bool:
|
||||
"""Does the open scene file have unsaved changes?"""
|
||||
|
||||
return bpy.data.is_dirty
|
||||
|
||||
|
||||
def file_extensions() -> List[str]:
|
||||
"""Return the supported file extensions for Blender scene files."""
|
||||
|
||||
return api.HOST_WORKFILE_EXTENSIONS["blender"]
|
||||
|
||||
|
||||
def work_root(session: dict) -> str:
|
||||
"""Return the default root to browse for work files."""
|
||||
|
||||
work_dir = session["AVALON_WORKDIR"]
|
||||
scene_dir = session.get("AVALON_SCENEDIR")
|
||||
if scene_dir:
|
||||
return str(Path(work_dir, scene_dir))
|
||||
return work_dir
|
||||
4
openpype/hosts/blender/blender_addon/startup/init.py
Normal file
4
openpype/hosts/blender/blender_addon/startup/init.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from avalon import pipeline
|
||||
from openpype.hosts.blender import api
|
||||
|
||||
pipeline.install(api)
|
||||
|
|
@ -4,7 +4,7 @@ import bpy
|
|||
|
||||
from avalon import api
|
||||
import openpype.hosts.blender.api.plugin
|
||||
from avalon.blender import lib
|
||||
from openpype.hosts.blender.api import lib
|
||||
|
||||
|
||||
class CreateAction(openpype.hosts.blender.api.plugin.Creator):
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@
|
|||
import bpy
|
||||
|
||||
from avalon import api
|
||||
from avalon.blender import lib, ops
|
||||
from avalon.blender.pipeline import AVALON_INSTANCES
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from openpype.hosts.blender.api import plugin, lib, ops
|
||||
from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES
|
||||
|
||||
|
||||
class CreateAnimation(plugin.Creator):
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@
|
|||
import bpy
|
||||
|
||||
from avalon import api
|
||||
from avalon.blender import lib, ops
|
||||
from avalon.blender.pipeline import AVALON_INSTANCES
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from openpype.hosts.blender.api import plugin, lib, ops
|
||||
from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES
|
||||
|
||||
|
||||
class CreateCamera(plugin.Creator):
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@
|
|||
import bpy
|
||||
|
||||
from avalon import api
|
||||
from avalon.blender import lib, ops
|
||||
from avalon.blender.pipeline import AVALON_INSTANCES
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from openpype.hosts.blender.api import plugin, lib, ops
|
||||
from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES
|
||||
|
||||
|
||||
class CreateLayout(plugin.Creator):
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@
|
|||
import bpy
|
||||
|
||||
from avalon import api
|
||||
from avalon.blender import lib, ops
|
||||
from avalon.blender.pipeline import AVALON_INSTANCES
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from openpype.hosts.blender.api import plugin, lib, ops
|
||||
from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES
|
||||
|
||||
|
||||
class CreateModel(plugin.Creator):
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
import bpy
|
||||
|
||||
from avalon import api
|
||||
from avalon.blender import lib
|
||||
import openpype.hosts.blender.api.plugin
|
||||
from openpype.hosts.blender.api import lib
|
||||
|
||||
|
||||
class CreatePointcache(openpype.hosts.blender.api.plugin.Creator):
|
||||
|
|
|
|||
|
|
@ -3,9 +3,8 @@
|
|||
import bpy
|
||||
|
||||
from avalon import api
|
||||
from avalon.blender import lib, ops
|
||||
from avalon.blender.pipeline import AVALON_INSTANCES
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from openpype.hosts.blender.api import plugin, lib, ops
|
||||
from openpype.hosts.blender.api.pipeline import AVALON_INSTANCES
|
||||
|
||||
|
||||
class CreateRig(plugin.Creator):
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ from typing import Dict, List, Optional
|
|||
import bpy
|
||||
|
||||
from avalon import api
|
||||
from avalon.blender import lib
|
||||
from avalon.blender.pipeline import AVALON_CONTAINERS
|
||||
from avalon.blender.pipeline import AVALON_CONTAINER_ID
|
||||
from avalon.blender.pipeline import AVALON_PROPERTY
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from openpype.hosts.blender.api.pipeline import (
|
||||
AVALON_CONTAINERS,
|
||||
AVALON_PROPERTY,
|
||||
AVALON_CONTAINER_ID
|
||||
)
|
||||
from openpype.hosts.blender.api import plugin, lib
|
||||
|
||||
|
||||
class CacheModelLoader(plugin.AssetLoader):
|
||||
|
|
|
|||
|
|
@ -1,16 +1,11 @@
|
|||
"""Load an animation in Blender."""
|
||||
|
||||
import logging
|
||||
from typing import Dict, List, Optional
|
||||
|
||||
import bpy
|
||||
|
||||
from avalon.blender.pipeline import AVALON_PROPERTY
|
||||
from openpype.hosts.blender.api import plugin
|
||||
|
||||
|
||||
logger = logging.getLogger("openpype").getChild(
|
||||
"blender").getChild("load_animation")
|
||||
from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY
|
||||
|
||||
|
||||
class BlendAnimationLoader(plugin.AssetLoader):
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ from typing import Dict, List, Optional
|
|||
import bpy
|
||||
|
||||
from avalon import api
|
||||
from avalon.blender.pipeline import AVALON_CONTAINERS
|
||||
from avalon.blender.pipeline import AVALON_CONTAINER_ID
|
||||
from avalon.blender.pipeline import AVALON_PROPERTY
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from openpype.hosts.blender.api.pipeline import (
|
||||
AVALON_CONTAINERS,
|
||||
AVALON_PROPERTY,
|
||||
AVALON_CONTAINER_ID
|
||||
)
|
||||
|
||||
|
||||
class AudioLoader(plugin.AssetLoader):
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ from typing import Dict, List, Optional
|
|||
import bpy
|
||||
|
||||
from avalon import api
|
||||
from avalon.blender.pipeline import AVALON_CONTAINERS
|
||||
from avalon.blender.pipeline import AVALON_CONTAINER_ID
|
||||
from avalon.blender.pipeline import AVALON_PROPERTY
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from openpype.hosts.blender.api.pipeline import (
|
||||
AVALON_CONTAINERS,
|
||||
AVALON_PROPERTY,
|
||||
AVALON_CONTAINER_ID
|
||||
)
|
||||
|
||||
logger = logging.getLogger("openpype").getChild(
|
||||
"blender").getChild("load_camera")
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ from typing import Dict, List, Optional
|
|||
import bpy
|
||||
|
||||
from avalon import api
|
||||
from avalon.blender import lib
|
||||
from avalon.blender.pipeline import AVALON_CONTAINERS
|
||||
from avalon.blender.pipeline import AVALON_CONTAINER_ID
|
||||
from avalon.blender.pipeline import AVALON_PROPERTY
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from openpype.hosts.blender.api import plugin, lib
|
||||
from openpype.hosts.blender.api.pipeline import (
|
||||
AVALON_CONTAINERS,
|
||||
AVALON_PROPERTY,
|
||||
AVALON_CONTAINER_ID
|
||||
)
|
||||
|
||||
|
||||
class FbxCameraLoader(plugin.AssetLoader):
|
||||
|
|
|
|||
|
|
@ -7,11 +7,12 @@ from typing import Dict, List, Optional
|
|||
import bpy
|
||||
|
||||
from avalon import api
|
||||
from avalon.blender import lib
|
||||
from avalon.blender.pipeline import AVALON_CONTAINERS
|
||||
from avalon.blender.pipeline import AVALON_CONTAINER_ID
|
||||
from avalon.blender.pipeline import AVALON_PROPERTY
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from openpype.hosts.blender.api import plugin, lib
|
||||
from openpype.hosts.blender.api.pipeline import (
|
||||
AVALON_CONTAINERS,
|
||||
AVALON_PROPERTY,
|
||||
AVALON_CONTAINER_ID
|
||||
)
|
||||
|
||||
|
||||
class FbxModelLoader(plugin.AssetLoader):
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ from typing import Dict, List, Optional
|
|||
import bpy
|
||||
|
||||
from avalon import api
|
||||
from avalon.blender.pipeline import AVALON_CONTAINERS
|
||||
from avalon.blender.pipeline import AVALON_CONTAINER_ID
|
||||
from avalon.blender.pipeline import AVALON_PROPERTY
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from openpype.hosts.blender.api.pipeline import (
|
||||
AVALON_CONTAINERS,
|
||||
AVALON_PROPERTY,
|
||||
AVALON_CONTAINER_ID
|
||||
)
|
||||
|
||||
|
||||
class BlendLayoutLoader(plugin.AssetLoader):
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
"""Load a layout in Blender."""
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
from pprint import pformat
|
||||
from typing import Dict, Optional
|
||||
|
||||
import bpy
|
||||
import json
|
||||
|
||||
from avalon import api
|
||||
from avalon.blender.pipeline import AVALON_CONTAINERS
|
||||
from avalon.blender.pipeline import AVALON_CONTAINER_ID
|
||||
from avalon.blender.pipeline import AVALON_PROPERTY
|
||||
from avalon.blender.pipeline import AVALON_INSTANCES
|
||||
from openpype import lib
|
||||
from openpype.hosts.blender.api.pipeline import (
|
||||
AVALON_INSTANCES,
|
||||
AVALON_CONTAINERS,
|
||||
AVALON_PROPERTY,
|
||||
AVALON_CONTAINER_ID
|
||||
)
|
||||
from openpype.hosts.blender.api import plugin
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -8,8 +8,12 @@ import os
|
|||
import json
|
||||
import bpy
|
||||
|
||||
from avalon import api, blender
|
||||
import openpype.hosts.blender.api.plugin as plugin
|
||||
from avalon import api
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from openpype.hosts.blender.api.pipeline import (
|
||||
containerise_existing,
|
||||
AVALON_PROPERTY
|
||||
)
|
||||
|
||||
|
||||
class BlendLookLoader(plugin.AssetLoader):
|
||||
|
|
@ -105,7 +109,7 @@ class BlendLookLoader(plugin.AssetLoader):
|
|||
|
||||
container = bpy.data.collections.new(lib_container)
|
||||
container.name = container_name
|
||||
blender.pipeline.containerise_existing(
|
||||
containerise_existing(
|
||||
container,
|
||||
name,
|
||||
namespace,
|
||||
|
|
@ -113,7 +117,7 @@ class BlendLookLoader(plugin.AssetLoader):
|
|||
self.__class__.__name__,
|
||||
)
|
||||
|
||||
metadata = container.get(blender.pipeline.AVALON_PROPERTY)
|
||||
metadata = container.get(AVALON_PROPERTY)
|
||||
|
||||
metadata["libpath"] = libpath
|
||||
metadata["lib_container"] = lib_container
|
||||
|
|
@ -161,7 +165,7 @@ class BlendLookLoader(plugin.AssetLoader):
|
|||
f"Unsupported file: {libpath}"
|
||||
)
|
||||
|
||||
collection_metadata = collection.get(blender.pipeline.AVALON_PROPERTY)
|
||||
collection_metadata = collection.get(AVALON_PROPERTY)
|
||||
collection_libpath = collection_metadata["libpath"]
|
||||
|
||||
normalized_collection_libpath = (
|
||||
|
|
@ -204,7 +208,7 @@ class BlendLookLoader(plugin.AssetLoader):
|
|||
if not collection:
|
||||
return False
|
||||
|
||||
collection_metadata = collection.get(blender.pipeline.AVALON_PROPERTY)
|
||||
collection_metadata = collection.get(AVALON_PROPERTY)
|
||||
|
||||
for obj in collection_metadata['objects']:
|
||||
for child in self.get_all_children(obj):
|
||||
|
|
|
|||
|
|
@ -7,10 +7,12 @@ from typing import Dict, List, Optional
|
|||
import bpy
|
||||
|
||||
from avalon import api
|
||||
from avalon.blender.pipeline import AVALON_CONTAINERS
|
||||
from avalon.blender.pipeline import AVALON_CONTAINER_ID
|
||||
from avalon.blender.pipeline import AVALON_PROPERTY
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from openpype.hosts.blender.api.pipeline import (
|
||||
AVALON_CONTAINERS,
|
||||
AVALON_PROPERTY,
|
||||
AVALON_CONTAINER_ID
|
||||
)
|
||||
|
||||
|
||||
class BlendModelLoader(plugin.AssetLoader):
|
||||
|
|
|
|||
|
|
@ -7,11 +7,13 @@ from typing import Dict, List, Optional
|
|||
import bpy
|
||||
|
||||
from avalon import api
|
||||
from avalon.blender.pipeline import AVALON_CONTAINERS
|
||||
from avalon.blender.pipeline import AVALON_CONTAINER_ID
|
||||
from avalon.blender.pipeline import AVALON_PROPERTY
|
||||
from openpype import lib
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from openpype.hosts.blender.api.pipeline import (
|
||||
AVALON_CONTAINERS,
|
||||
AVALON_PROPERTY,
|
||||
AVALON_CONTAINER_ID
|
||||
)
|
||||
|
||||
|
||||
class BlendRigLoader(plugin.AssetLoader):
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import json
|
||||
from typing import Generator
|
||||
|
||||
import bpy
|
||||
import json
|
||||
|
||||
import pyblish.api
|
||||
from avalon.blender.pipeline import AVALON_PROPERTY
|
||||
from avalon.blender.pipeline import AVALON_INSTANCES
|
||||
from openpype.hosts.blender.api.pipeline import (
|
||||
AVALON_INSTANCES,
|
||||
AVALON_PROPERTY,
|
||||
)
|
||||
|
||||
|
||||
class CollectInstances(pyblish.api.ContextPlugin):
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import os
|
||||
|
||||
import bpy
|
||||
|
||||
from openpype import api
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from avalon.blender.pipeline import AVALON_PROPERTY
|
||||
|
||||
import bpy
|
||||
from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY
|
||||
|
||||
|
||||
class ExtractABC(api.Extractor):
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ import os
|
|||
|
||||
import bpy
|
||||
|
||||
# import avalon.blender.workio
|
||||
import openpype.api
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import os
|
||||
|
||||
import bpy
|
||||
|
||||
from openpype import api
|
||||
from openpype.hosts.blender.api import plugin
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
class ExtractCamera(api.Extractor):
|
||||
"""Extract as the camera as FBX."""
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import os
|
||||
|
||||
import bpy
|
||||
|
||||
from openpype import api
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from avalon.blender.pipeline import AVALON_PROPERTY
|
||||
|
||||
import bpy
|
||||
from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY
|
||||
|
||||
|
||||
class ExtractFBX(api.Extractor):
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import bpy_extras.anim_utils
|
|||
|
||||
from openpype import api
|
||||
from openpype.hosts.blender.api import plugin
|
||||
from avalon.blender.pipeline import AVALON_PROPERTY
|
||||
from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY
|
||||
|
||||
|
||||
class ExtractAnimationFBX(api.Extractor):
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import json
|
|||
import bpy
|
||||
|
||||
from avalon import io
|
||||
from avalon.blender.pipeline import AVALON_PROPERTY
|
||||
from openpype.hosts.blender.api.pipeline import AVALON_PROPERTY
|
||||
import openpype.api
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import pyblish.api
|
||||
import avalon.blender.workio
|
||||
from openpype.hosts.blender.api.workio import save_file
|
||||
|
||||
|
||||
class IncrementWorkfileVersion(pyblish.api.ContextPlugin):
|
||||
|
|
@ -20,6 +20,6 @@ class IncrementWorkfileVersion(pyblish.api.ContextPlugin):
|
|||
path = context.data["currentFile"]
|
||||
filepath = version_up(path)
|
||||
|
||||
avalon.blender.workio.save_file(filepath, copy=False)
|
||||
save_file(filepath, copy=False)
|
||||
|
||||
self.log.info('Incrementing script version')
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
from openpype.hosts.blender import api
|
||||
|
||||
api.install()
|
||||
|
|
@ -4,6 +4,8 @@ import os
|
|||
import json
|
||||
import appdirs
|
||||
import requests
|
||||
import six
|
||||
import sys
|
||||
|
||||
from maya import cmds
|
||||
import maya.app.renderSetup.model.renderSetup as renderSetup
|
||||
|
|
@ -12,7 +14,15 @@ from openpype.hosts.maya.api import (
|
|||
lib,
|
||||
plugin
|
||||
)
|
||||
from openpype.api import get_system_settings
|
||||
from openpype.api import (
|
||||
get_system_settings,
|
||||
get_project_settings
|
||||
)
|
||||
|
||||
from openpype.modules import ModulesManager
|
||||
|
||||
from avalon.api import Session
|
||||
from avalon.api import CreatorError
|
||||
|
||||
|
||||
class CreateVRayScene(plugin.Creator):
|
||||
|
|
@ -22,11 +32,40 @@ class CreateVRayScene(plugin.Creator):
|
|||
family = "vrayscene"
|
||||
icon = "cubes"
|
||||
|
||||
_project_settings = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
"""Entry."""
|
||||
super(CreateVRayScene, self).__init__(*args, **kwargs)
|
||||
self._rs = renderSetup.instance()
|
||||
self.data["exportOnFarm"] = False
|
||||
deadline_settings = get_system_settings()["modules"]["deadline"]
|
||||
if not deadline_settings["enabled"]:
|
||||
self.deadline_servers = {}
|
||||
return
|
||||
self._project_settings = get_project_settings(
|
||||
Session["AVALON_PROJECT"])
|
||||
|
||||
try:
|
||||
default_servers = deadline_settings["deadline_urls"]
|
||||
project_servers = (
|
||||
self._project_settings["deadline"]["deadline_servers"]
|
||||
)
|
||||
self.deadline_servers = {
|
||||
k: default_servers[k]
|
||||
for k in project_servers
|
||||
if k in default_servers
|
||||
}
|
||||
|
||||
if not self.deadline_servers:
|
||||
self.deadline_servers = default_servers
|
||||
|
||||
except AttributeError:
|
||||
# Handle situation were we had only one url for deadline.
|
||||
manager = ModulesManager()
|
||||
deadline_module = manager.modules_by_name["deadline"]
|
||||
# get default deadline webservice url from deadline module
|
||||
self.deadline_servers = deadline_module.deadline_urls
|
||||
|
||||
def process(self):
|
||||
"""Entry point."""
|
||||
|
|
@ -37,10 +76,10 @@ class CreateVRayScene(plugin.Creator):
|
|||
use_selection = self.options.get("useSelection")
|
||||
with lib.undo_chunk():
|
||||
self._create_vray_instance_settings()
|
||||
instance = super(CreateVRayScene, self).process()
|
||||
self.instance = super(CreateVRayScene, self).process()
|
||||
|
||||
index = 1
|
||||
namespace_name = "_{}".format(str(instance))
|
||||
namespace_name = "_{}".format(str(self.instance))
|
||||
try:
|
||||
cmds.namespace(rm=namespace_name)
|
||||
except RuntimeError:
|
||||
|
|
@ -48,10 +87,19 @@ class CreateVRayScene(plugin.Creator):
|
|||
pass
|
||||
|
||||
while(cmds.namespace(exists=namespace_name)):
|
||||
namespace_name = "_{}{}".format(str(instance), index)
|
||||
namespace_name = "_{}{}".format(str(self.instance), index)
|
||||
index += 1
|
||||
|
||||
namespace = cmds.namespace(add=namespace_name)
|
||||
|
||||
# add Deadline server selection list
|
||||
if self.deadline_servers:
|
||||
cmds.scriptJob(
|
||||
attributeChange=[
|
||||
"{}.deadlineServers".format(self.instance),
|
||||
self._deadline_webservice_changed
|
||||
])
|
||||
|
||||
# create namespace with instance
|
||||
layers = self._rs.getRenderLayers()
|
||||
if use_selection:
|
||||
|
|
@ -62,7 +110,7 @@ class CreateVRayScene(plugin.Creator):
|
|||
render_set = cmds.sets(
|
||||
n="{}:{}".format(namespace, layer.name()))
|
||||
sets.append(render_set)
|
||||
cmds.sets(sets, forceElement=instance)
|
||||
cmds.sets(sets, forceElement=self.instance)
|
||||
|
||||
# if no render layers are present, create default one with
|
||||
# asterix selector
|
||||
|
|
@ -71,6 +119,52 @@ class CreateVRayScene(plugin.Creator):
|
|||
collection = render_layer.createCollection("defaultCollection")
|
||||
collection.getSelector().setPattern('*')
|
||||
|
||||
def _deadline_webservice_changed(self):
|
||||
"""Refresh Deadline server dependent options."""
|
||||
# get selected server
|
||||
from maya import cmds
|
||||
webservice = self.deadline_servers[
|
||||
self.server_aliases[
|
||||
cmds.getAttr("{}.deadlineServers".format(self.instance))
|
||||
]
|
||||
]
|
||||
pools = self._get_deadline_pools(webservice)
|
||||
cmds.deleteAttr("{}.primaryPool".format(self.instance))
|
||||
cmds.deleteAttr("{}.secondaryPool".format(self.instance))
|
||||
cmds.addAttr(self.instance, longName="primaryPool",
|
||||
attributeType="enum",
|
||||
enumName=":".join(pools))
|
||||
cmds.addAttr(self.instance, longName="secondaryPool",
|
||||
attributeType="enum",
|
||||
enumName=":".join(["-"] + pools))
|
||||
|
||||
def _get_deadline_pools(self, webservice):
|
||||
# type: (str) -> list
|
||||
"""Get pools from Deadline.
|
||||
Args:
|
||||
webservice (str): Server url.
|
||||
Returns:
|
||||
list: Pools.
|
||||
Throws:
|
||||
RuntimeError: If deadline webservice is unreachable.
|
||||
|
||||
"""
|
||||
argument = "{}/api/pools?NamesOnly=true".format(webservice)
|
||||
try:
|
||||
response = self._requests_get(argument)
|
||||
except requests.exceptions.ConnectionError as exc:
|
||||
msg = 'Cannot connect to deadline web service'
|
||||
self.log.error(msg)
|
||||
six.reraise(
|
||||
CreatorError,
|
||||
CreatorError('{} - {}'.format(msg, exc)),
|
||||
sys.exc_info()[2])
|
||||
if not response.ok:
|
||||
self.log.warning("No pools retrieved")
|
||||
return []
|
||||
|
||||
return response.json()
|
||||
|
||||
def _create_vray_instance_settings(self):
|
||||
# get pools
|
||||
pools = []
|
||||
|
|
@ -79,31 +173,29 @@ class CreateVRayScene(plugin.Creator):
|
|||
|
||||
deadline_enabled = system_settings["deadline"]["enabled"]
|
||||
muster_enabled = system_settings["muster"]["enabled"]
|
||||
deadline_url = system_settings["deadline"]["DEADLINE_REST_URL"]
|
||||
muster_url = system_settings["muster"]["MUSTER_REST_URL"]
|
||||
|
||||
if deadline_enabled and muster_enabled:
|
||||
self.log.error(
|
||||
"Both Deadline and Muster are enabled. " "Cannot support both."
|
||||
)
|
||||
raise RuntimeError("Both Deadline and Muster are enabled")
|
||||
raise CreatorError("Both Deadline and Muster are enabled")
|
||||
|
||||
self.server_aliases = self.deadline_servers.keys()
|
||||
self.data["deadlineServers"] = self.server_aliases
|
||||
|
||||
if deadline_enabled:
|
||||
argument = "{}/api/pools?NamesOnly=true".format(deadline_url)
|
||||
# if default server is not between selected, use first one for
|
||||
# initial list of pools.
|
||||
try:
|
||||
response = self._requests_get(argument)
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
msg = 'Cannot connect to deadline web service'
|
||||
self.log.error(msg)
|
||||
raise RuntimeError('{} - {}'.format(msg, e))
|
||||
if not response.ok:
|
||||
self.log.warning("No pools retrieved")
|
||||
else:
|
||||
pools = response.json()
|
||||
self.data["primaryPool"] = pools
|
||||
# We add a string "-" to allow the user to not
|
||||
# set any secondary pools
|
||||
self.data["secondaryPool"] = ["-"] + pools
|
||||
deadline_url = self.deadline_servers["default"]
|
||||
except KeyError:
|
||||
deadline_url = [
|
||||
self.deadline_servers[k]
|
||||
for k in self.deadline_servers.keys()
|
||||
][0]
|
||||
|
||||
pool_names = self._get_deadline_pools(deadline_url)
|
||||
|
||||
if muster_enabled:
|
||||
self.log.info(">>> Loading Muster credentials ...")
|
||||
|
|
@ -115,10 +207,10 @@ class CreateVRayScene(plugin.Creator):
|
|||
if e.startswith("401"):
|
||||
self.log.warning("access token expired")
|
||||
self._show_login()
|
||||
raise RuntimeError("Access token expired")
|
||||
raise CreatorError("Access token expired")
|
||||
except requests.exceptions.ConnectionError:
|
||||
self.log.error("Cannot connect to Muster API endpoint.")
|
||||
raise RuntimeError("Cannot connect to {}".format(muster_url))
|
||||
raise CreatorError("Cannot connect to {}".format(muster_url))
|
||||
pool_names = []
|
||||
for pool in pools:
|
||||
self.log.info(" - pool: {}".format(pool["name"]))
|
||||
|
|
@ -140,7 +232,7 @@ class CreateVRayScene(plugin.Creator):
|
|||
``MUSTER_PASSWORD``, ``MUSTER_REST_URL`` is loaded from presets.
|
||||
|
||||
Raises:
|
||||
RuntimeError: If loaded credentials are invalid.
|
||||
CreatorError: If loaded credentials are invalid.
|
||||
AttributeError: If ``MUSTER_REST_URL`` is not set.
|
||||
|
||||
"""
|
||||
|
|
@ -152,7 +244,7 @@ class CreateVRayScene(plugin.Creator):
|
|||
self._token = muster_json.get("token", None)
|
||||
if not self._token:
|
||||
self._show_login()
|
||||
raise RuntimeError("Invalid access token for Muster")
|
||||
raise CreatorError("Invalid access token for Muster")
|
||||
file.close()
|
||||
self.MUSTER_REST_URL = os.environ.get("MUSTER_REST_URL")
|
||||
if not self.MUSTER_REST_URL:
|
||||
|
|
@ -162,7 +254,7 @@ class CreateVRayScene(plugin.Creator):
|
|||
"""Get render pools from Muster.
|
||||
|
||||
Raises:
|
||||
Exception: If pool list cannot be obtained from Muster.
|
||||
CreatorError: If pool list cannot be obtained from Muster.
|
||||
|
||||
"""
|
||||
params = {"authToken": self._token}
|
||||
|
|
@ -178,12 +270,12 @@ class CreateVRayScene(plugin.Creator):
|
|||
("Cannot get pools from "
|
||||
"Muster: {}").format(response.status_code)
|
||||
)
|
||||
raise Exception("Cannot get pools from Muster")
|
||||
raise CreatorError("Cannot get pools from Muster")
|
||||
try:
|
||||
pools = response.json()["ResponseData"]["pools"]
|
||||
except ValueError as e:
|
||||
self.log.error("Invalid response from Muster server {}".format(e))
|
||||
raise Exception("Invalid response from Muster server")
|
||||
raise CreatorError("Invalid response from Muster server")
|
||||
|
||||
return pools
|
||||
|
||||
|
|
@ -196,7 +288,7 @@ class CreateVRayScene(plugin.Creator):
|
|||
login_response = self._requests_get(api_url, timeout=1)
|
||||
if login_response.status_code != 200:
|
||||
self.log.error("Cannot show login form to Muster")
|
||||
raise Exception("Cannot show login form to Muster")
|
||||
raise CreatorError("Cannot show login form to Muster")
|
||||
|
||||
def _requests_post(self, *args, **kwargs):
|
||||
"""Wrap request post method.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from maya import cmds
|
|||
|
||||
import pyblish.api
|
||||
from avalon import api
|
||||
from openpype.hosts.maya import lib
|
||||
from openpype.hosts.maya.api import lib
|
||||
|
||||
|
||||
class CollectVrayScene(pyblish.api.InstancePlugin):
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ class CollectRemoteInstances(pyblish.api.ContextPlugin):
|
|||
instance.data["family"] = resolved_family
|
||||
instance.data["publish"] = layer.visible
|
||||
instance.data["asset"] = context.data["assetEntity"]["name"]
|
||||
instance.data["task"] = context.data["taskType"]
|
||||
instance.data["task"] = context.data["task"]
|
||||
|
||||
fill_pairs = {
|
||||
"variant": variant,
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class CollectOtioReview(pyblish.api.InstancePlugin):
|
|||
|
||||
# loop all tracks and match with name in `reviewTrack`
|
||||
for track in otio_timeline.tracks:
|
||||
if review_track_name not in track.name:
|
||||
if review_track_name != track.name:
|
||||
continue
|
||||
|
||||
# process correct track
|
||||
|
|
|
|||
|
|
@ -308,6 +308,7 @@
|
|||
"family_filter_profiles": [
|
||||
{
|
||||
"hosts": [],
|
||||
"is_include": true,
|
||||
"task_types": [],
|
||||
"filter_families": []
|
||||
}
|
||||
|
|
|
|||
|
|
@ -267,7 +267,9 @@
|
|||
"label": "Task types"
|
||||
},
|
||||
{
|
||||
"type": "splitter"
|
||||
"type": "boolean",
|
||||
"key": "is_include",
|
||||
"label": "Exclude / Include"
|
||||
},
|
||||
{
|
||||
"type": "template",
|
||||
|
|
|
|||
|
|
@ -10,23 +10,39 @@
|
|||
"multiselection": "{multiselection}",
|
||||
"type": "enum",
|
||||
"enum_items": [
|
||||
{"action": "action"},
|
||||
{"animation": "animation"},
|
||||
{"audio": "audio"},
|
||||
{"camera": "camera"},
|
||||
{"editorial": "editorial"},
|
||||
{"layout": "layout"},
|
||||
{"look": "look"},
|
||||
{"mayaAscii": "mayaAscii"},
|
||||
{"model": "model"},
|
||||
{"pointcache": "pointcache"},
|
||||
{"reference": "reference"},
|
||||
{"render": "render"},
|
||||
{"review": "review"},
|
||||
{"rig": "rig"},
|
||||
{"setdress": "setdress"},
|
||||
{"workfile": "workfile"},
|
||||
{"xgen": "xgen"}
|
||||
{"action": "action"},
|
||||
{"animation": "animation"},
|
||||
{"assembly": "assembly"},
|
||||
{"audio": "audio"},
|
||||
{"backgroundComp": "backgroundComp"},
|
||||
{"backgroundLayout": "backgroundLayout"},
|
||||
{"camera": "camera"},
|
||||
{"editorial": "editorial"},
|
||||
{"gizmo": "gizmo"},
|
||||
{"image": "image"},
|
||||
{"layout": "layout"},
|
||||
{"look": "look"},
|
||||
{"matchmove": "matchmove"},
|
||||
{"mayaScene": "mayaScene"},
|
||||
{"model": "model"},
|
||||
{"nukenodes": "nukenodes"},
|
||||
{"plate": "plate"},
|
||||
{"pointcache": "pointcache"},
|
||||
{"prerender": "prerender"},
|
||||
{"redshiftproxy": "redshiftproxy"},
|
||||
{"reference": "reference"},
|
||||
{"render": "render"},
|
||||
{"review": "review"},
|
||||
{"rig": "rig"},
|
||||
{"setdress": "setdress"},
|
||||
{"take": "take"},
|
||||
{"usdShade": "usdShade"},
|
||||
{"vdbcache": "vdbcache"},
|
||||
{"vrayproxy": "vrayproxy"},
|
||||
{"workfile": "workfile"},
|
||||
{"xgen": "xgen"},
|
||||
{"yetiRig": "yetiRig"},
|
||||
{"yeticache": "yeticache"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -231,6 +231,7 @@ class FamilyConfigCache:
|
|||
self.dbcon = dbcon
|
||||
self.family_configs = {}
|
||||
self._family_filters_set = False
|
||||
self._family_filters_is_include = True
|
||||
self._require_refresh = True
|
||||
|
||||
@classmethod
|
||||
|
|
@ -252,7 +253,7 @@ class FamilyConfigCache:
|
|||
"icon": self.default_icon()
|
||||
}
|
||||
if self._family_filters_set:
|
||||
item["state"] = False
|
||||
item["state"] = not self._family_filters_is_include
|
||||
return item
|
||||
|
||||
def refresh(self, force=False):
|
||||
|
|
@ -316,20 +317,23 @@ class FamilyConfigCache:
|
|||
matching_item = filter_profiles(profiles, profiles_filter)
|
||||
|
||||
families = []
|
||||
is_include = True
|
||||
if matching_item:
|
||||
families = matching_item["filter_families"]
|
||||
is_include = matching_item["is_include"]
|
||||
|
||||
if not families:
|
||||
return
|
||||
|
||||
self._family_filters_set = True
|
||||
self._family_filters_is_include = is_include
|
||||
|
||||
# Replace icons with a Qt icon we can use in the user interfaces
|
||||
for family in families:
|
||||
family_info = {
|
||||
"name": family,
|
||||
"icon": self.default_icon(),
|
||||
"state": True
|
||||
"state": is_include
|
||||
}
|
||||
|
||||
self.family_configs[family] = family_info
|
||||
|
|
|
|||
123
poetry.lock
generated
123
poetry.lock
generated
|
|
@ -789,11 +789,11 @@ six = "*"
|
|||
|
||||
[[package]]
|
||||
name = "pillow"
|
||||
version = "8.4.0"
|
||||
version = "9.0.0"
|
||||
description = "Python Imaging Library (Fork)"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = ">=3.6"
|
||||
python-versions = ">=3.7"
|
||||
|
||||
[[package]]
|
||||
name = "platformdirs"
|
||||
|
|
@ -1580,7 +1580,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "3.7.*"
|
||||
content-hash = "877c1c6292735f495d915fc6aa85450eb20fc63f266a9c6bf7ba1125af3579a5"
|
||||
content-hash = "79f05a4232aae63e024dd16678ccb173318cbdca5b54e04d320f36fd5d916b5c"
|
||||
|
||||
[metadata.files]
|
||||
acre = []
|
||||
|
|
@ -1668,10 +1668,13 @@ babel = [
|
|||
{file = "Babel-2.9.1.tar.gz", hash = "sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0"},
|
||||
]
|
||||
bcrypt = [
|
||||
{file = "bcrypt-3.2.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b589229207630484aefe5899122fb938a5b017b0f4349f769b8c13e78d99a8fd"},
|
||||
{file = "bcrypt-3.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6"},
|
||||
{file = "bcrypt-3.2.0-cp36-abi3-manylinux1_x86_64.whl", hash = "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7"},
|
||||
{file = "bcrypt-3.2.0-cp36-abi3-manylinux2010_x86_64.whl", hash = "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1"},
|
||||
{file = "bcrypt-3.2.0-cp36-abi3-manylinux2014_aarch64.whl", hash = "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d"},
|
||||
{file = "bcrypt-3.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:a0584a92329210fcd75eb8a3250c5a941633f8bfaf2a18f81009b097732839b7"},
|
||||
{file = "bcrypt-3.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:56e5da069a76470679f312a7d3d23deb3ac4519991a0361abc11da837087b61d"},
|
||||
{file = "bcrypt-3.2.0-cp36-abi3-win32.whl", hash = "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55"},
|
||||
{file = "bcrypt-3.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34"},
|
||||
{file = "bcrypt-3.2.0.tar.gz", hash = "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29"},
|
||||
|
|
@ -1972,12 +1975,28 @@ log4mongo = [
|
|||
{file = "log4mongo-1.7.0.tar.gz", hash = "sha256:dc374617206162a0b14167fbb5feac01dbef587539a235dadba6200362984a68"},
|
||||
]
|
||||
markupsafe = [
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-win32.whl", hash = "sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28"},
|
||||
{file = "MarkupSafe-2.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-win32.whl", hash = "sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d"},
|
||||
{file = "MarkupSafe-2.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567"},
|
||||
|
|
@ -1986,14 +2005,27 @@ markupsafe = [
|
|||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-win32.whl", hash = "sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415"},
|
||||
{file = "MarkupSafe-2.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-win32.whl", hash = "sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64"},
|
||||
{file = "MarkupSafe-2.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26"},
|
||||
|
|
@ -2003,6 +2035,12 @@ markupsafe = [
|
|||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_i686.whl", hash = "sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-win32.whl", hash = "sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74"},
|
||||
{file = "MarkupSafe-2.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8"},
|
||||
{file = "MarkupSafe-2.0.1.tar.gz", hash = "sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a"},
|
||||
|
|
@ -2103,47 +2141,38 @@ pathlib2 = [
|
|||
{file = "pathlib2-2.3.6.tar.gz", hash = "sha256:7d8bcb5555003cdf4a8d2872c538faa3a0f5d20630cb360e518ca3b981795e5f"},
|
||||
]
|
||||
pillow = [
|
||||
{file = "Pillow-8.4.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d"},
|
||||
{file = "Pillow-8.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3f97cfb1e5a392d75dd8b9fd274d205404729923840ca94ca45a0af57e13dbe6"},
|
||||
{file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb9fc393f3c61f9054e1ed26e6fe912c7321af2f41ff49d3f83d05bacf22cc78"},
|
||||
{file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d82cdb63100ef5eedb8391732375e6d05993b765f72cb34311fab92103314649"},
|
||||
{file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc1afda735a8d109007164714e73771b499768b9bb5afcbbee9d0ff374b43f"},
|
||||
{file = "Pillow-8.4.0-cp310-cp310-win32.whl", hash = "sha256:e3dacecfbeec9a33e932f00c6cd7996e62f53ad46fbe677577394aaa90ee419a"},
|
||||
{file = "Pillow-8.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:620582db2a85b2df5f8a82ddeb52116560d7e5e6b055095f04ad828d1b0baa39"},
|
||||
{file = "Pillow-8.4.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:1bc723b434fbc4ab50bb68e11e93ce5fb69866ad621e3c2c9bdb0cd70e345f55"},
|
||||
{file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72cbcfd54df6caf85cc35264c77ede902452d6df41166010262374155947460c"},
|
||||
{file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70ad9e5c6cb9b8487280a02c0ad8a51581dcbbe8484ce058477692a27c151c0a"},
|
||||
{file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25a49dc2e2f74e65efaa32b153527fc5ac98508d502fa46e74fa4fd678ed6645"},
|
||||
{file = "Pillow-8.4.0-cp36-cp36m-win32.whl", hash = "sha256:93ce9e955cc95959df98505e4608ad98281fff037350d8c2671c9aa86bcf10a9"},
|
||||
{file = "Pillow-8.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2e4440b8f00f504ee4b53fe30f4e381aae30b0568193be305256b1462216feff"},
|
||||
{file = "Pillow-8.4.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8c803ac3c28bbc53763e6825746f05cc407b20e4a69d0122e526a582e3b5e153"},
|
||||
{file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8a17b5d948f4ceeceb66384727dde11b240736fddeda54ca740b9b8b1556b29"},
|
||||
{file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1394a6ad5abc838c5cd8a92c5a07535648cdf6d09e8e2d6df916dfa9ea86ead8"},
|
||||
{file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:792e5c12376594bfcb986ebf3855aa4b7c225754e9a9521298e460e92fb4a488"},
|
||||
{file = "Pillow-8.4.0-cp37-cp37m-win32.whl", hash = "sha256:d99ec152570e4196772e7a8e4ba5320d2d27bf22fdf11743dd882936ed64305b"},
|
||||
{file = "Pillow-8.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:7b7017b61bbcdd7f6363aeceb881e23c46583739cb69a3ab39cb384f6ec82e5b"},
|
||||
{file = "Pillow-8.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:d89363f02658e253dbd171f7c3716a5d340a24ee82d38aab9183f7fdf0cdca49"},
|
||||
{file = "Pillow-8.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a0956fdc5defc34462bb1c765ee88d933239f9a94bc37d132004775241a7585"},
|
||||
{file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b7bb9de00197fb4261825c15551adf7605cf14a80badf1761d61e59da347779"},
|
||||
{file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72b9e656e340447f827885b8d7a15fc8c4e68d410dc2297ef6787eec0f0ea409"},
|
||||
{file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5a4532a12314149d8b4e4ad8ff09dde7427731fcfa5917ff16d0291f13609df"},
|
||||
{file = "Pillow-8.4.0-cp38-cp38-win32.whl", hash = "sha256:82aafa8d5eb68c8463b6e9baeb4f19043bb31fefc03eb7b216b51e6a9981ae09"},
|
||||
{file = "Pillow-8.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:066f3999cb3b070a95c3652712cffa1a748cd02d60ad7b4e485c3748a04d9d76"},
|
||||
{file = "Pillow-8.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:5503c86916d27c2e101b7f71c2ae2cddba01a2cf55b8395b0255fd33fa4d1f1a"},
|
||||
{file = "Pillow-8.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4acc0985ddf39d1bc969a9220b51d94ed51695d455c228d8ac29fcdb25810e6e"},
|
||||
{file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b052a619a8bfcf26bd8b3f48f45283f9e977890263e4571f2393ed8898d331b"},
|
||||
{file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:493cb4e415f44cd601fcec11c99836f707bb714ab03f5ed46ac25713baf0ff20"},
|
||||
{file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8831cb7332eda5dc89b21a7bce7ef6ad305548820595033a4b03cf3091235ed"},
|
||||
{file = "Pillow-8.4.0-cp39-cp39-win32.whl", hash = "sha256:5e9ac5f66616b87d4da618a20ab0a38324dbe88d8a39b55be8964eb520021e02"},
|
||||
{file = "Pillow-8.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:3eb1ce5f65908556c2d8685a8f0a6e989d887ec4057326f6c22b24e8a172c66b"},
|
||||
{file = "Pillow-8.4.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ddc4d832a0f0b4c52fff973a0d44b6c99839a9d016fe4e6a1cb8f3eea96479c2"},
|
||||
{file = "Pillow-8.4.0-pp36-pypy36_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a3e5ddc44c14042f0844b8cf7d2cd455f6cc80fd7f5eefbe657292cf601d9ad"},
|
||||
{file = "Pillow-8.4.0-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70e94281588ef053ae8998039610dbd71bc509e4acbc77ab59d7d2937b10698"},
|
||||
{file = "Pillow-8.4.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:3862b7256046fcd950618ed22d1d60b842e3a40a48236a5498746f21189afbbc"},
|
||||
{file = "Pillow-8.4.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4901622493f88b1a29bd30ec1a2f683782e57c3c16a2dbc7f2595ba01f639df"},
|
||||
{file = "Pillow-8.4.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c471a734240653a0ec91dec0996696eea227eafe72a33bd06c92697728046b"},
|
||||
{file = "Pillow-8.4.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:244cf3b97802c34c41905d22810846802a3329ddcb93ccc432870243211c79fc"},
|
||||
{file = "Pillow-8.4.0.tar.gz", hash = "sha256:b8e2f83c56e141920c39464b852de3719dfbfb6e3c99a2d8da0edf4fb33176ed"},
|
||||
{file = "Pillow-9.0.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:113723312215b25c22df1fdf0e2da7a3b9c357a7d24a93ebbe80bfda4f37a8d4"},
|
||||
{file = "Pillow-9.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bb47a548cea95b86494a26c89d153fd31122ed65255db5dcbc421a2d28eb3379"},
|
||||
{file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31b265496e603985fad54d52d11970383e317d11e18e856971bdbb86af7242a4"},
|
||||
{file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d154ed971a4cc04b93a6d5b47f37948d1f621f25de3e8fa0c26b2d44f24e3e8f"},
|
||||
{file = "Pillow-9.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80fe92813d208ce8aa7d76da878bdc84b90809f79ccbad2a288e9bcbeac1d9bd"},
|
||||
{file = "Pillow-9.0.0-cp310-cp310-win32.whl", hash = "sha256:d5dcea1387331c905405b09cdbfb34611050cc52c865d71f2362f354faee1e9f"},
|
||||
{file = "Pillow-9.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:52abae4c96b5da630a8b4247de5428f593465291e5b239f3f843a911a3cf0105"},
|
||||
{file = "Pillow-9.0.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:72c3110228944019e5f27232296c5923398496b28be42535e3b2dc7297b6e8b6"},
|
||||
{file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97b6d21771da41497b81652d44191489296555b761684f82b7b544c49989110f"},
|
||||
{file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72f649d93d4cc4d8cf79c91ebc25137c358718ad75f99e99e043325ea7d56100"},
|
||||
{file = "Pillow-9.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7aaf07085c756f6cb1c692ee0d5a86c531703b6e8c9cae581b31b562c16b98ce"},
|
||||
{file = "Pillow-9.0.0-cp37-cp37m-win32.whl", hash = "sha256:03b27b197deb4ee400ed57d8d4e572d2d8d80f825b6634daf6e2c18c3c6ccfa6"},
|
||||
{file = "Pillow-9.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:a09a9d4ec2b7887f7a088bbaacfd5c07160e746e3d47ec5e8050ae3b2a229e9f"},
|
||||
{file = "Pillow-9.0.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:490e52e99224858f154975db61c060686df8a6b3f0212a678e5d2e2ce24675c9"},
|
||||
{file = "Pillow-9.0.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:500d397ddf4bbf2ca42e198399ac13e7841956c72645513e8ddf243b31ad2128"},
|
||||
{file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ebd8b9137630a7bbbff8c4b31e774ff05bbb90f7911d93ea2c9371e41039b52"},
|
||||
{file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd0e5062f11cb3e730450a7d9f323f4051b532781026395c4323b8ad055523c4"},
|
||||
{file = "Pillow-9.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9f3b4522148586d35e78313db4db0df4b759ddd7649ef70002b6c3767d0fdeb7"},
|
||||
{file = "Pillow-9.0.0-cp38-cp38-win32.whl", hash = "sha256:0b281fcadbb688607ea6ece7649c5d59d4bbd574e90db6cd030e9e85bde9fecc"},
|
||||
{file = "Pillow-9.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:b5050d681bcf5c9f2570b93bee5d3ec8ae4cf23158812f91ed57f7126df91762"},
|
||||
{file = "Pillow-9.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:c2067b3bb0781f14059b112c9da5a91c80a600a97915b4f48b37f197895dd925"},
|
||||
{file = "Pillow-9.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2d16b6196fb7a54aff6b5e3ecd00f7c0bab1b56eee39214b2b223a9d938c50af"},
|
||||
{file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:98cb63ca63cb61f594511c06218ab4394bf80388b3d66cd61d0b1f63ee0ea69f"},
|
||||
{file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bc462d24500ba707e9cbdef436c16e5c8cbf29908278af053008d9f689f56dee"},
|
||||
{file = "Pillow-9.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3586e12d874ce2f1bc875a3ffba98732ebb12e18fb6d97be482bd62b56803281"},
|
||||
{file = "Pillow-9.0.0-cp39-cp39-win32.whl", hash = "sha256:68e06f8b2248f6dc8b899c3e7ecf02c9f413aab622f4d6190df53a78b93d97a5"},
|
||||
{file = "Pillow-9.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:6579f9ba84a3d4f1807c4aab4be06f373017fc65fff43498885ac50a9b47a553"},
|
||||
{file = "Pillow-9.0.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:47f5cf60bcb9fbc46011f75c9b45a8b5ad077ca352a78185bd3e7f1d294b98bb"},
|
||||
{file = "Pillow-9.0.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fd8053e1f8ff1844419842fd474fc359676b2e2a2b66b11cc59f4fa0a301315"},
|
||||
{file = "Pillow-9.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c5439bfb35a89cac50e81c751317faea647b9a3ec11c039900cd6915831064d"},
|
||||
{file = "Pillow-9.0.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:95545137fc56ce8c10de646074d242001a112a92de169986abd8c88c27566a05"},
|
||||
{file = "Pillow-9.0.0.tar.gz", hash = "sha256:ee6e2963e92762923956fe5d3479b1fdc3b76c83f290aad131a2f98c3df0593e"},
|
||||
]
|
||||
platformdirs = [
|
||||
{file = "platformdirs-2.4.0-py3-none-any.whl", hash = "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"},
|
||||
|
|
@ -2392,6 +2421,8 @@ pynput = [
|
|||
]
|
||||
pyobjc-core = [
|
||||
{file = "pyobjc-core-7.3.tar.gz", hash = "sha256:5081aedf8bb40aac1a8ad95adac9e44e148a882686ded614adf46bb67fd67574"},
|
||||
{file = "pyobjc_core-7.3-1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a1f1e6b457127cbf2b5bd2b94520a7c89fb590b739911eadb2b0499a3a5b0e6f"},
|
||||
{file = "pyobjc_core-7.3-1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:ed708cc47bae8b711f81f252af09898a5f986c7a38cec5ad5623d571d328bff8"},
|
||||
{file = "pyobjc_core-7.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4e93ad769a20b908778fe950f62a843a6d8f0fa71996e5f3cc9fab5ae7d17771"},
|
||||
{file = "pyobjc_core-7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f63fd37bbf3785af4ddb2f86cad5ca81c62cfc7d1c0099637ca18343c3656c1"},
|
||||
{file = "pyobjc_core-7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9b1311f72f2e170742a7ee3a8149f52c35158dc024a21e88d6f1e52ba5d718b"},
|
||||
|
|
@ -2410,6 +2441,8 @@ pyobjc-framework-applicationservices = [
|
|||
]
|
||||
pyobjc-framework-cocoa = [
|
||||
{file = "pyobjc-framework-Cocoa-7.3.tar.gz", hash = "sha256:b18d05e7a795a3455ad191c3e43d6bfa673c2a4fd480bb1ccf57191051b80b7e"},
|
||||
{file = "pyobjc_framework_Cocoa-7.3-1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1e31376806e5de883a1d7c7c87d9ff2a8b09fc05d267e0dfce6e42409fb70c67"},
|
||||
{file = "pyobjc_framework_Cocoa-7.3-1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d999387927284346035cb63ebb51f86331abc41f9376f9a6970e7f18207db392"},
|
||||
{file = "pyobjc_framework_Cocoa-7.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9edffdfa6dd1f71f21b531c3e61fdd3e4d5d3bf6c5a528c98e88828cd60bac11"},
|
||||
{file = "pyobjc_framework_Cocoa-7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:35a6340437a4e0109a302150b7d1f6baf57004ccf74834f9e6062fcafe2fd8d7"},
|
||||
{file = "pyobjc_framework_Cocoa-7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7c3886f2608ab3ed02482f8b2ebf9f782b324c559e84b52cfd92dba8a1109872"},
|
||||
|
|
@ -2418,6 +2451,8 @@ pyobjc-framework-cocoa = [
|
|||
]
|
||||
pyobjc-framework-quartz = [
|
||||
{file = "pyobjc-framework-Quartz-7.3.tar.gz", hash = "sha256:98812844c34262def980bdf60923a875cd43428a8375b6fd53bd2cd800eccf0b"},
|
||||
{file = "pyobjc_framework_Quartz-7.3-1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1139bc6874c0f8b58f0b8602015e0994198bc506a6bcec1071208de32b55ed26"},
|
||||
{file = "pyobjc_framework_Quartz-7.3-1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:d94a3ed7051266c52392ec07d3b5adbf28d4be83341a24df0d88639344dcd84f"},
|
||||
{file = "pyobjc_framework_Quartz-7.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1ef18f5a16511ded65980bf4f5983ea5d35c88224dbad1b3112abd29c60413ea"},
|
||||
{file = "pyobjc_framework_Quartz-7.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b41eec8d4b10c7c7e011e2f9051367f5499ef315ba52dfbae573c3a2e05469c"},
|
||||
{file = "pyobjc_framework_Quartz-7.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c65456ed045dfe1711d0298734e5a3ad670f8c770f7eb3b19979256c388bdd2"},
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ jsonschema = "^3.2.0"
|
|||
keyring = "^22.0.1"
|
||||
log4mongo = "^1.7"
|
||||
pathlib2= "^2.3.5" # deadline submit publish job only (single place, maybe not needed?)
|
||||
Pillow = "^8.3" # only used for slates prototype
|
||||
Pillow = "^9.0" # used in TVPaint and for slates
|
||||
pyblish-base = "^1.8.8"
|
||||
pynput = "^1.7.2" # idle manager in tray
|
||||
pymongo = "^3.11.2"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue