Merge branch 'develop' into enhancement/AY-974_Nuke-simplified-deadline-submission-on-write-node

This commit is contained in:
Toke Jepsen 2024-05-23 22:40:59 +01:00 committed by GitHub
commit 8eb5c2a99e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
96 changed files with 607 additions and 106 deletions

View file

@ -51,6 +51,8 @@ IGNORED_MODULES_IN_AYON = set()
# - this is used to log the missing addon # - this is used to log the missing addon
MOVED_ADDON_MILESTONE_VERSIONS = { MOVED_ADDON_MILESTONE_VERSIONS = {
"applications": VersionInfo(0, 2, 0), "applications": VersionInfo(0, 2, 0),
"clockify": VersionInfo(0, 2, 0),
"tvpaint": VersionInfo(0, 2, 0),
} }
# Inherit from `object` for Python 2 hosts # Inherit from `object` for Python 2 hosts

View file

@ -365,3 +365,62 @@ def maintained_time():
yield yield
finally: finally:
bpy.context.scene.frame_current = current_time bpy.context.scene.frame_current = current_time
def get_all_parents(obj):
"""Get all recursive parents of object.
Arguments:
obj (bpy.types.Object): Object to get all parents for.
Returns:
List[bpy.types.Object]: All parents of object
"""
result = []
while True:
obj = obj.parent
if not obj:
break
result.append(obj)
return result
def get_highest_root(objects):
"""Get the highest object (the least parents) among the objects.
If multiple objects have the same amount of parents (or no parents) the
first object found in the input iterable will be returned.
Note that this will *not* return objects outside of the input list, as
such it will not return the root of node from a child node. It is purely
intended to find the highest object among a list of objects. To instead
get the root from one object use, e.g. `get_all_parents(obj)[-1]`
Arguments:
objects (List[bpy.types.Object]): Objects to find the highest root in.
Returns:
Optional[bpy.types.Object]: First highest root found or None if no
`bpy.types.Object` found in input list.
"""
included_objects = {obj.name_full for obj in objects}
num_parents_to_obj = {}
for obj in objects:
if isinstance(obj, bpy.types.Object):
parents = get_all_parents(obj)
# included parents
parents = [parent for parent in parents if
parent.name_full in included_objects]
if not parents:
# A node without parents must be a highest root
return obj
num_parents_to_obj.setdefault(len(parents), obj)
if not num_parents_to_obj:
return
minimum_parent = min(num_parents_to_obj)
return num_parents_to_obj[minimum_parent]

View file

@ -26,7 +26,8 @@ from .ops import (
) )
from .lib import imprint from .lib import imprint
VALID_EXTENSIONS = [".blend", ".json", ".abc", ".fbx"] VALID_EXTENSIONS = [".blend", ".json", ".abc", ".fbx",
".usd", ".usdc", ".usda"]
def prepare_scene_name( def prepare_scene_name(

View file

@ -0,0 +1,30 @@
"""Create a USD Export."""
from ayon_core.hosts.blender.api import plugin, lib
class CreateUSD(plugin.BaseCreator):
"""Create USD Export"""
identifier = "io.openpype.creators.blender.usd"
name = "usdMain"
label = "USD"
product_type = "usd"
icon = "gears"
def create(
self, product_name: str, instance_data: dict, pre_create_data: dict
):
# Run parent create method
collection = super().create(
product_name, instance_data, pre_create_data
)
if pre_create_data.get("use_selection"):
objects = lib.get_selection()
for obj in objects:
collection.objects.link(obj)
if obj.type == 'EMPTY':
objects.extend(obj.children)
return collection

View file

@ -26,10 +26,10 @@ class CacheModelLoader(plugin.AssetLoader):
Note: Note:
At least for now it only supports Alembic files. At least for now it only supports Alembic files.
""" """
product_types = {"model", "pointcache", "animation"} product_types = {"model", "pointcache", "animation", "usd"}
representations = {"abc"} representations = {"abc", "usd"}
label = "Load Alembic" label = "Load Cache"
icon = "code-fork" icon = "code-fork"
color = "orange" color = "orange"
@ -53,10 +53,21 @@ class CacheModelLoader(plugin.AssetLoader):
plugin.deselect_all() plugin.deselect_all()
relative = bpy.context.preferences.filepaths.use_relative_paths relative = bpy.context.preferences.filepaths.use_relative_paths
bpy.ops.wm.alembic_import(
filepath=libpath, if any(libpath.lower().endswith(ext)
relative_path=relative for ext in [".usd", ".usda", ".usdc"]):
) # USD
bpy.ops.wm.usd_import(
filepath=libpath,
relative_path=relative
)
else:
# Alembic
bpy.ops.wm.alembic_import(
filepath=libpath,
relative_path=relative
)
imported = lib.get_selection() imported = lib.get_selection()

View file

@ -12,7 +12,7 @@ class CollectBlenderInstanceData(pyblish.api.InstancePlugin):
order = pyblish.api.CollectorOrder order = pyblish.api.CollectorOrder
hosts = ["blender"] hosts = ["blender"]
families = ["model", "pointcache", "animation", "rig", "camera", "layout", families = ["model", "pointcache", "animation", "rig", "camera", "layout",
"blendScene"] "blendScene", "usd"]
label = "Collect Instance" label = "Collect Instance"
def process(self, instance): def process(self, instance):

View file

@ -0,0 +1,90 @@
import os
import bpy
from ayon_core.pipeline import publish
from ayon_core.hosts.blender.api import plugin, lib
class ExtractUSD(publish.Extractor):
"""Extract as USD."""
label = "Extract USD"
hosts = ["blender"]
families = ["usd"]
def process(self, instance):
# Ignore runtime instances (e.g. USD layers)
# TODO: This is better done via more specific `families`
if not instance.data.get("transientData", {}).get("instance_node"):
return
# Define extract output file path
stagingdir = self.staging_dir(instance)
filename = f"{instance.name}.usd"
filepath = os.path.join(stagingdir, filename)
# Perform extraction
self.log.debug("Performing extraction..")
# Select all members to "export selected"
plugin.deselect_all()
selected = []
for obj in instance:
if isinstance(obj, bpy.types.Object):
obj.select_set(True)
selected.append(obj)
root = lib.get_highest_root(objects=instance[:])
if not root:
instance_node = instance.data["transientData"]["instance_node"]
raise publish.KnownPublishError(
f"No root object found in instance: {instance_node.name}"
)
self.log.debug(f"Exporting using active root: {root.name}")
context = plugin.create_blender_context(
active=root, selected=selected)
# Export USD
with bpy.context.temp_override(**context):
bpy.ops.wm.usd_export(
filepath=filepath,
selected_objects_only=True,
export_textures=False,
relative_paths=False,
export_animation=False,
export_hair=False,
export_uvmaps=True,
# TODO: add for new version of Blender (4+?)
# export_mesh_colors=True,
export_normals=True,
export_materials=True,
use_instancing=True
)
plugin.deselect_all()
# Add representation
representation = {
'name': 'usd',
'ext': 'usd',
'files': filename,
"stagingDir": stagingdir,
}
instance.data.setdefault("representations", []).append(representation)
self.log.debug("Extracted instance '%s' to: %s",
instance.name, representation)
class ExtractModelUSD(ExtractUSD):
"""Extract model as USD."""
label = "Extract USD (Model)"
hosts = ["blender"]
families = ["model"]
# Driven by settings
optional = True

View file

@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
"""Creator plugin for creating Model product type.
Note:
Currently, This creator plugin is the same as 'create_pointcache.py'
But renaming the product type to 'model'.
It's purpose to support
Maya (load/publish model from maya to/from houdini).
It's considered to support multiple representations in the future.
"""
from ayon_core.hosts.houdini.api import plugin
from ayon_core.lib import BoolDef
import hou
class CreateModel(plugin.HoudiniCreator):
"""Create Model"""
identifier = "io.openpype.creators.houdini.model"
label = "Model"
product_type = "model"
icon = "cube"
def create(self, product_name, instance_data, pre_create_data):
instance_data.pop("active", None)
instance_data.update({"node_type": "alembic"})
creator_attributes = instance_data.setdefault(
"creator_attributes", dict())
creator_attributes["farm"] = pre_create_data["farm"]
instance = super(CreateModel, self).create(
product_name,
instance_data,
pre_create_data)
instance_node = hou.node(instance.get("instance_node"))
parms = {
"use_sop_path": True,
"build_from_path": True,
"path_attrib": "path",
"prim_to_detail_pattern": "cbId",
"format": 2,
"facesets": 0,
"filename": hou.text.expandString(
"$HIP/pyblish/{}.abc".format(product_name))
}
if self.selected_nodes:
selected_node = self.selected_nodes[0]
# Although Houdini allows ObjNode path on `sop_path` for the
# the ROP node we prefer it set to the SopNode path explicitly
# Allow sop level paths (e.g. /obj/geo1/box1)
if isinstance(selected_node, hou.SopNode):
parms["sop_path"] = selected_node.path()
self.log.debug(
"Valid SopNode selection, 'SOP Path' in ROP will be set to '%s'."
% selected_node.path()
)
# Allow object level paths to Geometry nodes (e.g. /obj/geo1)
# but do not allow other object level nodes types like cameras, etc.
elif isinstance(selected_node, hou.ObjNode) and \
selected_node.type().name() in ["geo"]:
# get the output node with the minimum
# 'outputidx' or the node with display flag
sop_path = self.get_obj_output(selected_node)
if sop_path:
parms["sop_path"] = sop_path.path()
self.log.debug(
"Valid ObjNode selection, 'SOP Path' in ROP will be set to "
"the child path '%s'."
% sop_path.path()
)
if not parms.get("sop_path", None):
self.log.debug(
"Selection isn't valid. 'SOP Path' in ROP will be empty."
)
else:
self.log.debug(
"No Selection. 'SOP Path' in ROP will be empty."
)
instance_node.setParms(parms)
instance_node.parm("trange").set(1)
# Explicitly set f1 and f2 to frame start.
# Which forces the rop node to export one frame.
instance_node.parmTuple('f').deleteAllKeyframes()
fstart = int(hou.hscriptExpression("$FSTART"))
instance_node.parmTuple('f').set((fstart, fstart, 1))
# Lock any parameters in this list
to_lock = ["prim_to_detail_pattern"]
self.lock_parameters(instance_node, to_lock)
def get_network_categories(self):
return [
hou.ropNodeTypeCategory(),
hou.sopNodeTypeCategory()
]
def get_obj_output(self, obj_node):
"""Find output node with the smallest 'outputidx'."""
outputs = obj_node.subnetOutputs()
# if obj_node is empty
if not outputs:
return
# if obj_node has one output child whether its
# sop output node or a node with the render flag
elif len(outputs) == 1:
return outputs[0]
# if there are more than one, then it have multiple output nodes
# return the one with the minimum 'outputidx'
else:
return min(outputs,
key=lambda node: node.evalParm('outputidx'))
def get_instance_attr_defs(self):
return [
BoolDef("farm",
label="Submitting to Farm",
default=False)
]
def get_pre_create_attr_defs(self):
attrs = super().get_pre_create_attr_defs()
# Use same attributes as for instance attributes
return attrs + self.get_instance_attr_defs()

View file

@ -11,7 +11,7 @@ class CollectDataforCache(pyblish.api.InstancePlugin):
order = pyblish.api.CollectorOrder + 0.11 order = pyblish.api.CollectorOrder + 0.11
families = ["ass", "pointcache", families = ["ass", "pointcache",
"mantraifd", "redshiftproxy", "mantraifd", "redshiftproxy",
"vdbcache"] "vdbcache", "model"]
hosts = ["houdini"] hosts = ["houdini"]
targets = ["local", "remote"] targets = ["local", "remote"]
label = "Collect Data for Cache" label = "Collect Data for Cache"
@ -43,10 +43,7 @@ class CollectDataforCache(pyblish.api.InstancePlugin):
cache_files = {"_": instance.data["files"]} cache_files = {"_": instance.data["files"]}
# Convert instance family to pointcache if it is bgeo or abc # Convert instance family to pointcache if it is bgeo or abc
# because ??? # because ???
for family in instance.data["families"]: self.log.debug(instance.data["families"])
if family == "bgeo" or "abc":
instance.data["productType"] = "pointcache"
break
instance.data.update({ instance.data.update({
"plugin": "Houdini", "plugin": "Houdini",
"publish": True "publish": True

View file

@ -10,7 +10,7 @@ class CollectChunkSize(pyblish.api.InstancePlugin,
order = pyblish.api.CollectorOrder + 0.05 order = pyblish.api.CollectorOrder + 0.05
families = ["ass", "pointcache", families = ["ass", "pointcache",
"vdbcache", "mantraifd", "vdbcache", "mantraifd",
"redshiftproxy"] "redshiftproxy", "model"]
hosts = ["houdini"] hosts = ["houdini"]
targets = ["local", "remote"] targets = ["local", "remote"]
label = "Collect Chunk Size" label = "Collect Chunk Size"

View file

@ -1,21 +1,24 @@
"""Collector for pointcache types. """Collector for different types.
This will add additional family to pointcache instance based on This will add additional families to different instance based on
the creator_identifier parameter. the creator_identifier parameter.
""" """
import pyblish.api import pyblish.api
class CollectPointcacheType(pyblish.api.InstancePlugin): class CollectPointcacheType(pyblish.api.InstancePlugin):
"""Collect data type for pointcache instance.""" """Collect data type for different instances."""
order = pyblish.api.CollectorOrder order = pyblish.api.CollectorOrder
hosts = ["houdini"] hosts = ["houdini"]
families = ["pointcache"] families = ["pointcache", "model"]
label = "Collect type of pointcache" label = "Collect instances types"
def process(self, instance): def process(self, instance):
if instance.data["creator_identifier"] == "io.openpype.creators.houdini.bgeo": # noqa: E501 if instance.data["creator_identifier"] == "io.openpype.creators.houdini.bgeo": # noqa: E501
instance.data["families"] += ["bgeo"] instance.data["families"] += ["bgeo"]
elif instance.data["creator_identifier"] == "io.openpype.creators.houdini.pointcache": # noqa: E501 elif instance.data["creator_identifier"] in {
"io.openpype.creators.houdini.pointcache",
"io.openpype.creators.houdini.model"
}:
instance.data["families"] += ["abc"] instance.data["families"] += ["abc"]

View file

@ -132,6 +132,6 @@ class CollectLocalRenderInstances(pyblish.api.InstancePlugin):
] ]
}) })
# Remove original render instance # Skip integrating original render instance.
# I can't remove it here as I still need it to trigger the render. # We are not removing it because it's used to trigger the render.
# context.remove(instance) instance.data["integrate"] = False

View file

@ -15,7 +15,8 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin):
"usd", "usd",
"usdrender", "usdrender",
"redshiftproxy", "redshiftproxy",
"staticMesh" "staticMesh",
"model"
] ]
hosts = ["houdini"] hosts = ["houdini"]

View file

@ -0,0 +1,60 @@
# -*- coding: utf-8 -*-
"""Validator for checking that export is a single frame."""
import pyblish.api
from ayon_core.pipeline import (
PublishValidationError,
OptionalPyblishPluginMixin
)
from ayon_core.pipeline.publish import ValidateContentsOrder
from ayon_core.hosts.houdini.api.action import SelectInvalidAction
class ValidateSingleFrame(pyblish.api.InstancePlugin,
OptionalPyblishPluginMixin):
"""Validate Export is a Single Frame.
It checks if rop node is exporting one frame.
This is mainly for Model product type.
"""
families = ["model"]
hosts = ["houdini"]
label = "Validate Single Frame"
order = ValidateContentsOrder + 0.1
actions = [SelectInvalidAction]
def process(self, instance):
invalid = self.get_invalid(instance)
if invalid:
nodes = [n.path() for n in invalid]
raise PublishValidationError(
"See log for details. "
"Invalid nodes: {0}".format(nodes)
)
@classmethod
def get_invalid(cls, instance):
invalid = []
frame_start = instance.data.get("frameStartHandle")
frame_end = instance.data.get("frameEndHandle")
# This happens if instance node has no 'trange' parameter.
if frame_start is None or frame_end is None:
cls.log.debug(
"No frame data, skipping check.."
)
return
if frame_start != frame_end:
invalid.append(instance.data["instance_node"])
cls.log.error(
"Invalid frame range on '%s'."
"You should use the same frame number for 'f1' "
"and 'f2' parameters.",
instance.data["instance_node"].path()
)
return invalid

View file

@ -16,9 +16,13 @@ class ValidateMeshIsStatic(pyblish.api.InstancePlugin,
"""Validate mesh is static. """Validate mesh is static.
It checks if output node is time dependent. It checks if output node is time dependent.
this avoids getting different output from ROP node when extracted
from a different frame than the first frame.
(Might be overly restrictive though)
""" """
families = ["staticMesh"] families = ["staticMesh",
"model"]
hosts = ["houdini"] hosts = ["houdini"]
label = "Validate Mesh is Static" label = "Validate Mesh is Static"
order = ValidateContentsOrder + 0.1 order = ValidateContentsOrder + 0.1

View file

@ -7,7 +7,7 @@ class ValidateIntermediateDirectoriesChecked(pyblish.api.InstancePlugin):
"""Validate Create Intermediate Directories is enabled on ROP node.""" """Validate Create Intermediate Directories is enabled on ROP node."""
order = pyblish.api.ValidatorOrder order = pyblish.api.ValidatorOrder
families = ["pointcache", "camera", "vdbcache"] families = ["pointcache", "camera", "vdbcache", "model"]
hosts = ["houdini"] hosts = ["houdini"]
label = "Create Intermediate Directories Checked" label = "Create Intermediate Directories Checked"

View file

@ -22,7 +22,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin):
""" """
order = pyblish.api.ValidatorOrder order = pyblish.api.ValidatorOrder
families = ["pointcache", "vdbcache"] families = ["pointcache", "vdbcache", "model"]
hosts = ["houdini"] hosts = ["houdini"]
label = "Validate Output Node (SOP)" label = "Validate Output Node (SOP)"
actions = [SelectROPAction, SelectInvalidAction] actions = [SelectROPAction, SelectInvalidAction]

View file

@ -1,5 +0,0 @@
from .clockify_module import ClockifyModule
__all__ = (
"ClockifyModule",
)

View file

@ -290,6 +290,34 @@ class ActionDelegate(QtWidgets.QStyledItemDelegate):
painter.drawPixmap(extender_x, extender_y, pix) painter.drawPixmap(extender_x, extender_y, pix)
class ActionsProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
def lessThan(self, left, right):
# Sort by action order and then by label
left_value = left.data(ACTION_SORT_ROLE)
right_value = right.data(ACTION_SORT_ROLE)
# Values are same -> use super sorting
if left_value == right_value:
# Default behavior is using DisplayRole
return super().lessThan(left, right)
# Validate 'None' values
if right_value is None:
return True
if left_value is None:
return False
# Sort values and handle incompatible types
try:
return left_value < right_value
except TypeError:
return True
class ActionsWidget(QtWidgets.QWidget): class ActionsWidget(QtWidgets.QWidget):
def __init__(self, controller, parent): def __init__(self, controller, parent):
super(ActionsWidget, self).__init__(parent) super(ActionsWidget, self).__init__(parent)
@ -316,10 +344,7 @@ class ActionsWidget(QtWidgets.QWidget):
model = ActionsQtModel(controller) model = ActionsQtModel(controller)
proxy_model = QtCore.QSortFilterProxyModel() proxy_model = ActionsProxyModel()
proxy_model.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
proxy_model.setSortRole(ACTION_SORT_ROLE)
proxy_model.setSourceModel(model) proxy_model.setSourceModel(model)
view.setModel(proxy_model) view.setModel(proxy_model)

View file

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""Package declaring AYON core addon version.""" """Package declaring AYON core addon version."""
__version__ = "0.3.2-dev.1" __version__ = "0.3.3-dev.1"

View file

@ -1,6 +1,6 @@
name = "core" name = "core"
title = "Core" title = "Core"
version = "0.3.2-dev.1" version = "0.3.3-dev.1"
client_dir = "ayon_core" client_dir = "ayon_core"

View file

@ -1,3 +1,3 @@
name = "blender" name = "blender"
title = "Blender" title = "Blender"
version = "0.1.8" version = "0.1.9"

View file

@ -151,6 +151,10 @@ class PublishPluginsModel(BaseSettingsModel):
default_factory=ExtractPlayblastModel, default_factory=ExtractPlayblastModel,
title="Extract Playblast" title="Extract Playblast"
) )
ExtractModelUSD: ValidatePluginModel = SettingsField(
default_factory=ValidatePluginModel,
title="Extract Model USD"
)
DEFAULT_BLENDER_PUBLISH_SETTINGS = { DEFAULT_BLENDER_PUBLISH_SETTINGS = {
@ -348,5 +352,10 @@ DEFAULT_BLENDER_PUBLISH_SETTINGS = {
}, },
indent=4 indent=4
) )
},
"ExtractModelUSD": {
"enabled": True,
"optional": True,
"active": True
} }
} }

View file

@ -0,0 +1,5 @@
from .addon import ClockifyAddon
__all__ = (
"ClockifyAddon",
)

View file

@ -2,12 +2,12 @@ import os
import threading import threading
import time import time
from ayon_core.modules import AYONAddon, ITrayModule, IPluginPaths from ayon_core.addon import AYONAddon, ITrayAddon, IPluginPaths
from .constants import CLOCKIFY_FTRACK_USER_PATH, CLOCKIFY_FTRACK_SERVER_PATH from .constants import CLOCKIFY_FTRACK_USER_PATH, CLOCKIFY_FTRACK_SERVER_PATH
class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths): class ClockifyAddon(AYONAddon, ITrayAddon, IPluginPaths):
name = "clockify" name = "clockify"
def initialize(self, studio_settings): def initialize(self, studio_settings):
@ -31,7 +31,7 @@ class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths):
# TimersManager attributes # TimersManager attributes
# - set `timers_manager_connector` only in `tray_init` # - set `timers_manager_connector` only in `tray_init`
self.timers_manager_connector = None self.timers_manager_connector = None
self._timers_manager_module = None self._timer_manager_addon = None
@property @property
def clockify_api(self): def clockify_api(self):
@ -87,7 +87,7 @@ class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths):
return {"actions": [actions_path]} return {"actions": [actions_path]}
def get_ftrack_event_handler_paths(self): def get_ftrack_event_handler_paths(self):
"""Function for Ftrack module to add ftrack event handler paths.""" """Function for ftrack addon to add ftrack event handler paths."""
return { return {
"user": [CLOCKIFY_FTRACK_USER_PATH], "user": [CLOCKIFY_FTRACK_USER_PATH],
"server": [CLOCKIFY_FTRACK_SERVER_PATH], "server": [CLOCKIFY_FTRACK_SERVER_PATH],
@ -206,19 +206,19 @@ class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths):
self.action_stop_timer.setVisible(self.bool_timer_run) self.action_stop_timer.setVisible(self.bool_timer_run)
# --- TimersManager connection methods --- # --- TimersManager connection methods ---
def register_timers_manager(self, timer_manager_module): def register_timers_manager(self, timer_manager_addon):
"""Store TimersManager for future use.""" """Store TimersManager for future use."""
self._timers_manager_module = timer_manager_module self._timer_manager_addon = timer_manager_addon
def timer_started(self, data): def timer_started(self, data):
"""Tell TimersManager that timer started.""" """Tell TimersManager that timer started."""
if self._timers_manager_module is not None: if self._timer_manager_addon is not None:
self._timers_manager_module.timer_started(self.id, data) self._timer_manager_addon.timer_started(self.id, data)
def timer_stopped(self): def timer_stopped(self):
"""Tell TimersManager that timer stopped.""" """Tell TimersManager that timer stopped."""
if self._timers_manager_module is not None: if self._timer_manager_addon is not None:
self._timers_manager_module.timer_stopped(self.id) self._timer_manager_addon.timer_stopped(self.id)
def stop_timer(self): def stop_timer(self):
"""Called from TimersManager to stop timer.""" """Called from TimersManager to stop timer."""

View file

@ -1,15 +1,17 @@
import os import os
import json import json
import datetime import datetime
import requests import requests
from ayon_core.lib.local_settings import AYONSecureRegistry
from ayon_core.lib import Logger
from .constants import ( from .constants import (
CLOCKIFY_ENDPOINT, CLOCKIFY_ENDPOINT,
ADMIN_PERMISSION_NAMES, ADMIN_PERMISSION_NAMES,
) )
from ayon_core.lib.local_settings import AYONSecureRegistry
from ayon_core.lib import Logger
class ClockifyAPI: class ClockifyAPI:
log = Logger.get_logger(__name__) log = Logger.get_logger(__name__)

View file

@ -1,7 +1,9 @@
import os import os
import json import json
from openpype_modules.ftrack.lib import ServerAction
from openpype_modules.clockify.clockify_api import ClockifyAPI from ayon_clockify.clockify_api import ClockifyAPI
from ayon_ftrack.lib import ServerAction
class SyncClockifyServer(ServerAction): class SyncClockifyServer(ServerAction):

View file

@ -1,25 +1,20 @@
import json import json
from openpype_modules.ftrack.lib import BaseAction, statics_icon from ayon_clockify.clockify_api import ClockifyAPI
from openpype_modules.clockify.clockify_api import ClockifyAPI from ayon_ftrack.lib import BaseAction, statics_icon
class SyncClockifyLocal(BaseAction): class SyncClockifyLocal(BaseAction):
'''Synchronise project names and task types.''' """Synchronise project names and task types."""
#: Action identifier. identifier = "clockify.sync.local"
identifier = 'clockify.sync.local' label = "Sync To Clockify"
#: Action label. description = "Synchronise data to Clockify workspace"
label = 'Sync To Clockify (local)'
#: Action description.
description = 'Synchronise data to Clockify workspace'
#: roles that are allowed to register this action
role_list = ["Administrator", "project Manager"] role_list = ["Administrator", "project Manager"]
#: icon
icon = statics_icon("app_icons", "clockify-white.png") icon = statics_icon("app_icons", "clockify-white.png")
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(SyncClockifyLocal, self).__init__(*args, **kwargs) super(SyncClockifyLocal, self).__init__(*args, **kwargs)
#: CLockifyApi
self.clockify_api = ClockifyAPI() self.clockify_api = ClockifyAPI()
def discover(self, session, entities, event): def discover(self, session, entities, event):
@ -56,7 +51,7 @@ class SyncClockifyLocal(BaseAction):
'user': user, 'user': user,
'status': 'running', 'status': 'running',
'data': json.dumps({ 'data': json.dumps({
'description': 'Sync Ftrack to Clockify' 'description': 'Sync ftrack to Clockify'
}) })
}) })
session.commit() session.commit()

View file

@ -1,7 +1,8 @@
import ayon_api import ayon_api
from ayon_clockify.clockify_api import ClockifyAPI
from ayon_core.pipeline import LauncherAction from ayon_core.pipeline import LauncherAction
from openpype_modules.clockify.clockify_api import ClockifyAPI
class ClockifyStart(LauncherAction): class ClockifyStart(LauncherAction):

View file

@ -1,6 +1,6 @@
import ayon_api import ayon_api
from openpype_modules.clockify.clockify_api import ClockifyAPI from ayon_clockify.clockify_api import ClockifyAPI
from ayon_core.pipeline import LauncherAction from ayon_core.pipeline import LauncherAction

View file

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

View file

@ -1,3 +1,9 @@
name = "clockify" name = "clockify"
title = "Clockify" title = "Clockify"
version = "0.1.1" version = "0.2.0"
client_dir = "ayon_clockify"
ayon_required_addons = {
"core": ">0.3.2",
}
ayon_compatible_addons = {}

View file

@ -47,7 +47,7 @@ plugin_for = ["ayon_server"]
""" """
CLIENT_VERSION_CONTENT = '''# -*- coding: utf-8 -*- CLIENT_VERSION_CONTENT = '''# -*- coding: utf-8 -*-
"""Package declaring AYON core addon version.""" """Package declaring AYON addon '{}' version."""
__version__ = "{}" __version__ = "{}"
''' '''
@ -183,6 +183,7 @@ def create_addon_zip(
def prepare_client_code( def prepare_client_code(
addon_name: str,
addon_dir: Path, addon_dir: Path,
addon_output_dir: Path, addon_output_dir: Path,
addon_version: str addon_version: str
@ -211,7 +212,9 @@ def prepare_client_code(
version_path = subpath / "version.py" version_path = subpath / "version.py"
if version_path.exists(): if version_path.exists():
with open(version_path, "w") as stream: with open(version_path, "w") as stream:
stream.write(CLIENT_VERSION_CONTENT.format(addon_version)) stream.write(
CLIENT_VERSION_CONTENT.format(addon_name, addon_version)
)
zip_filepath = private_dir / "client.zip" zip_filepath = private_dir / "client.zip"
with ZipFileLongPaths(zip_filepath, "w", zipfile.ZIP_DEFLATED) as zipf: with ZipFileLongPaths(zip_filepath, "w", zipfile.ZIP_DEFLATED) as zipf:
@ -262,7 +265,9 @@ def create_addon_package(
server_dir, addon_output_dir / "server", dirs_exist_ok=True server_dir, addon_output_dir / "server", dirs_exist_ok=True
) )
prepare_client_code(addon_dir, addon_output_dir, addon_version) prepare_client_code(
package.name, addon_dir, addon_output_dir, addon_version
)
if create_zip: if create_zip:
create_addon_zip( create_addon_zip(

View file

@ -57,6 +57,9 @@ class CreatePluginsModel(BaseSettingsModel):
CreateMantraROP: CreatorModel = SettingsField( CreateMantraROP: CreatorModel = SettingsField(
default_factory=CreatorModel, default_factory=CreatorModel,
title="Create Mantra ROP") title="Create Mantra ROP")
CreateModel: CreatorModel = SettingsField(
default_factory=CreatorModel,
title="Create Model")
CreatePointCache: CreatorModel = SettingsField( CreatePointCache: CreatorModel = SettingsField(
default_factory=CreatorModel, default_factory=CreatorModel,
title="Create PointCache (Abc)") title="Create PointCache (Abc)")
@ -124,6 +127,10 @@ DEFAULT_HOUDINI_CREATE_SETTINGS = {
"enabled": True, "enabled": True,
"default_variants": ["Main"] "default_variants": ["Main"]
}, },
"CreateModel": {
"enabled": True,
"default_variants": ["Main"]
},
"CreatePointCache": { "CreatePointCache": {
"enabled": True, "enabled": True,
"default_variants": ["Main"] "default_variants": ["Main"]

View file

@ -22,7 +22,7 @@ from aiohttp_json_rpc.protocol import (
from aiohttp_json_rpc.exceptions import RpcError from aiohttp_json_rpc.exceptions import RpcError
from ayon_core.lib import emit_event from ayon_core.lib import emit_event
from ayon_core.hosts.tvpaint.tvpaint_plugin import get_plugin_files_path from ayon_tvpaint.tvpaint_plugin import get_plugin_files_path
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
log.setLevel(logging.DEBUG) log.setLevel(logging.DEBUG)

View file

@ -10,7 +10,7 @@ from qtpy import QtWidgets, QtCore, QtGui
from ayon_core import style from ayon_core import style
from ayon_core.pipeline import install_host from ayon_core.pipeline import install_host
from ayon_core.hosts.tvpaint.api import ( from ayon_tvpaint.api import (
TVPaintHost, TVPaintHost,
CommunicationWrapper, CommunicationWrapper,
) )

View file

@ -7,8 +7,9 @@ import requests
import ayon_api import ayon_api
import pyblish.api import pyblish.api
from ayon_tvpaint import TVPAINT_ROOT_DIR
from ayon_core.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost from ayon_core.host import HostBase, IWorkfileHost, ILoadHost, IPublishHost
from ayon_core.hosts.tvpaint import TVPAINT_ROOT_DIR
from ayon_core.settings import get_current_project_settings from ayon_core.settings import get_current_project_settings
from ayon_core.lib import register_event_callback from ayon_core.lib import register_event_callback
from ayon_core.pipeline import ( from ayon_core.pipeline import (

View file

@ -12,7 +12,7 @@ from ayon_core.pipeline.create.creator_plugins import cache_and_get_instances
from .lib import get_layers_data from .lib import get_layers_data
SHARED_DATA_KEY = "openpype.tvpaint.instances" SHARED_DATA_KEY = "ayon.tvpaint.instances"
class TVPaintCreatorCommon: class TVPaintCreatorCommon:
@ -89,6 +89,8 @@ class TVPaintCreatorCommon:
class TVPaintCreator(Creator, TVPaintCreatorCommon): class TVPaintCreator(Creator, TVPaintCreatorCommon):
settings_category = "tvpaint"
def collect_instances(self): def collect_instances(self):
self._collect_create_instances() self._collect_create_instances()
@ -140,6 +142,8 @@ class TVPaintCreator(Creator, TVPaintCreatorCommon):
class TVPaintAutoCreator(AutoCreator, TVPaintCreatorCommon): class TVPaintAutoCreator(AutoCreator, TVPaintCreatorCommon):
settings_category = "tvpaint"
def collect_instances(self): def collect_instances(self):
self._collect_create_instances() self._collect_create_instances()
@ -152,6 +156,7 @@ class TVPaintAutoCreator(AutoCreator, TVPaintCreatorCommon):
class Loader(LoaderPlugin): class Loader(LoaderPlugin):
hosts = ["tvpaint"] hosts = ["tvpaint"]
settings_category = "tvpaint"
@staticmethod @staticmethod
def get_members_from_container(container): def get_members_from_container(container):

View file

@ -37,6 +37,6 @@ class TvpaintPrelaunchHook(PreLaunchHook):
self.launch_context.launch_args.extend(remainders) self.launch_context.launch_args.extend(remainders)
def launch_script_path(self): def launch_script_path(self):
from ayon_core.hosts.tvpaint import get_launch_script_path from ayon_tvpaint import get_launch_script_path
return get_launch_script_path() return get_launch_script_path()

View file

@ -4,8 +4,8 @@ from ayon_core.pipeline.create.creator_plugins import (
ProductConvertorPlugin, ProductConvertorPlugin,
cache_and_get_instances, cache_and_get_instances,
) )
from ayon_core.hosts.tvpaint.api.plugin import SHARED_DATA_KEY from ayon_tvpaint.api.plugin import SHARED_DATA_KEY
from ayon_core.hosts.tvpaint.api.lib import get_groups_data from ayon_tvpaint.api.lib import get_groups_data
class TVPaintLegacyConverted(ProductConvertorPlugin): class TVPaintLegacyConverted(ProductConvertorPlugin):

View file

@ -52,11 +52,11 @@ from ayon_core.pipeline.create import (
CreatedInstance, CreatedInstance,
CreatorError, CreatorError,
) )
from ayon_core.hosts.tvpaint.api.plugin import ( from ayon_tvpaint.api.plugin import (
TVPaintCreator, TVPaintCreator,
TVPaintAutoCreator, TVPaintAutoCreator,
) )
from ayon_core.hosts.tvpaint.api.lib import ( from ayon_tvpaint.api.lib import (
get_layers_data, get_layers_data,
get_groups_data, get_groups_data,
execute_george_through_file, execute_george_through_file,

View file

@ -1,7 +1,7 @@
import ayon_api import ayon_api
from ayon_core.pipeline import CreatedInstance from ayon_core.pipeline import CreatedInstance
from ayon_core.hosts.tvpaint.api.plugin import TVPaintAutoCreator from ayon_tvpaint.api.plugin import TVPaintAutoCreator
class TVPaintReviewCreator(TVPaintAutoCreator): class TVPaintReviewCreator(TVPaintAutoCreator):

View file

@ -1,7 +1,7 @@
import ayon_api import ayon_api
from ayon_core.pipeline import CreatedInstance from ayon_core.pipeline import CreatedInstance
from ayon_core.hosts.tvpaint.api.plugin import TVPaintAutoCreator from ayon_tvpaint.api.plugin import TVPaintAutoCreator
class TVPaintWorkfileCreator(TVPaintAutoCreator): class TVPaintWorkfileCreator(TVPaintAutoCreator):

View file

@ -1,6 +1,6 @@
from ayon_core.lib.attribute_definitions import BoolDef from ayon_core.lib.attribute_definitions import BoolDef
from ayon_core.hosts.tvpaint.api import plugin from ayon_tvpaint.api import plugin
from ayon_core.hosts.tvpaint.api.lib import execute_george_through_file from ayon_tvpaint.api.lib import execute_george_through_file
class ImportImage(plugin.Loader): class ImportImage(plugin.Loader):

View file

@ -2,12 +2,12 @@ import collections
from ayon_core.lib.attribute_definitions import BoolDef from ayon_core.lib.attribute_definitions import BoolDef
from ayon_core.pipeline import registered_host from ayon_core.pipeline import registered_host
from ayon_core.hosts.tvpaint.api import plugin from ayon_tvpaint.api import plugin
from ayon_core.hosts.tvpaint.api.lib import ( from ayon_tvpaint.api.lib import (
get_layers_data, get_layers_data,
execute_george_through_file, execute_george_through_file,
) )
from ayon_core.hosts.tvpaint.api.pipeline import ( from ayon_tvpaint.api.pipeline import (
write_workfile_metadata, write_workfile_metadata,
SECTION_NAME_CONTAINERS, SECTION_NAME_CONTAINERS,
containerise, containerise,

View file

@ -1,7 +1,7 @@
import os import os
import tempfile import tempfile
from ayon_core.hosts.tvpaint.api import plugin from ayon_tvpaint.api import plugin
from ayon_core.hosts.tvpaint.api.lib import ( from ayon_tvpaint.api.lib import (
execute_george_through_file, execute_george_through_file,
) )

View file

@ -10,11 +10,11 @@ from ayon_core.pipeline.workfile import (
get_last_workfile_with_version, get_last_workfile_with_version,
) )
from ayon_core.pipeline.template_data import get_template_data_with_names from ayon_core.pipeline.template_data import get_template_data_with_names
from ayon_core.hosts.tvpaint.api import plugin from ayon_tvpaint.api import plugin
from ayon_core.hosts.tvpaint.api.lib import ( from ayon_tvpaint.api.lib import (
execute_george_through_file, execute_george_through_file,
) )
from ayon_core.hosts.tvpaint.api.pipeline import ( from ayon_tvpaint.api.pipeline import (
get_current_workfile_context, get_current_workfile_context,
) )
from ayon_core.pipeline.version_start import get_versioning_start from ayon_core.pipeline.version_start import get_versioning_start

View file

@ -14,6 +14,8 @@ class CollectOutputFrameRange(pyblish.api.InstancePlugin):
hosts = ["tvpaint"] hosts = ["tvpaint"]
families = ["review", "render"] families = ["review", "render"]
settings_category = "tvpaint"
def process(self, instance): def process(self, instance):
folder_entity = instance.data.get("folderEntity") folder_entity = instance.data.get("folderEntity")
if not folder_entity: if not folder_entity:

View file

@ -9,6 +9,7 @@ class CollectRenderInstances(pyblish.api.InstancePlugin):
hosts = ["tvpaint"] hosts = ["tvpaint"]
families = ["render", "review"] families = ["render", "review"]
settings_category = "tvpaint"
ignore_render_pass_transparency = False ignore_render_pass_transparency = False
def process(self, instance): def process(self, instance):

View file

@ -9,6 +9,8 @@ class CollectWorkfile(pyblish.api.InstancePlugin):
hosts = ["tvpaint"] hosts = ["tvpaint"]
families = ["workfile"] families = ["workfile"]
settings_category = "tvpaint"
def process(self, instance): def process(self, instance):
context = instance.context context = instance.context
current_file = context.data["currentFile"] current_file = context.data["currentFile"]

View file

@ -4,13 +4,13 @@ import tempfile
import pyblish.api import pyblish.api
from ayon_core.hosts.tvpaint.api.lib import ( from ayon_tvpaint.api.lib import (
execute_george, execute_george,
execute_george_through_file, execute_george_through_file,
get_layers_data, get_layers_data,
get_groups_data, get_groups_data,
) )
from ayon_core.hosts.tvpaint.api.pipeline import ( from ayon_tvpaint.api.pipeline import (
SECTION_NAME_CONTEXT, SECTION_NAME_CONTEXT,
SECTION_NAME_INSTANCES, SECTION_NAME_INSTANCES,
SECTION_NAME_CONTAINERS, SECTION_NAME_CONTAINERS,
@ -58,6 +58,8 @@ class CollectWorkfileData(pyblish.api.ContextPlugin):
hosts = ["tvpaint"] hosts = ["tvpaint"]
actions = [ResetTVPaintWorkfileMetadata] actions = [ResetTVPaintWorkfileMetadata]
settings_category = "tvpaint"
def process(self, context): def process(self, context):
current_project_id = execute_george("tv_projectcurrentid") current_project_id = execute_george("tv_projectcurrentid")
execute_george("tv_projectselect {}".format(current_project_id)) execute_george("tv_projectselect {}".format(current_project_id))

View file

@ -23,6 +23,8 @@ class ExtractConvertToEXR(pyblish.api.InstancePlugin):
hosts = ["tvpaint"] hosts = ["tvpaint"]
families = ["render"] families = ["render"]
settings_category = "tvpaint"
enabled = False enabled = False
# Replace source PNG files or just add # Replace source PNG files or just add

View file

@ -10,13 +10,13 @@ from ayon_core.pipeline.publish import (
KnownPublishError, KnownPublishError,
get_publish_instance_families, get_publish_instance_families,
) )
from ayon_core.hosts.tvpaint.api.lib import ( from ayon_tvpaint.api.lib import (
execute_george, execute_george,
execute_george_through_file, execute_george_through_file,
get_layers_pre_post_behavior, get_layers_pre_post_behavior,
get_layers_exposure_frames, get_layers_exposure_frames,
) )
from ayon_core.hosts.tvpaint.lib import ( from ayon_tvpaint.lib import (
calculate_layers_extraction_data, calculate_layers_extraction_data,
get_frame_filename_template, get_frame_filename_template,
fill_reference_frames, fill_reference_frames,
@ -31,6 +31,8 @@ class ExtractSequence(pyblish.api.InstancePlugin):
hosts = ["tvpaint"] hosts = ["tvpaint"]
families = ["review", "render"] families = ["review", "render"]
settings_category = "tvpaint"
# Modifiable with settings # Modifiable with settings
review_bg = [255, 255, 255, 1.0] review_bg = [255, 255, 255, 1.0]

View file

@ -12,6 +12,8 @@ class IncrementWorkfileVersion(pyblish.api.ContextPlugin):
optional = True optional = True
hosts = ["tvpaint"] hosts = ["tvpaint"]
settings_category = "tvpaint"
def process(self, context): def process(self, context):
assert all(result["success"] for result in context.data["results"]), ( assert all(result["success"] for result in context.data["results"]), (

View file

@ -3,7 +3,7 @@ from ayon_core.pipeline import (
PublishXmlValidationError, PublishXmlValidationError,
OptionalPyblishPluginMixin, OptionalPyblishPluginMixin,
) )
from ayon_core.hosts.tvpaint.api.pipeline import ( from ayon_tvpaint.api.pipeline import (
list_instances, list_instances,
write_instances, write_instances,
) )
@ -48,6 +48,8 @@ class ValidateAssetName(
hosts = ["tvpaint"] hosts = ["tvpaint"]
actions = [FixFolderPaths] actions = [FixFolderPaths]
settings_category = "tvpaint"
def process(self, context): def process(self, context):
if not self.is_active(context.data): if not self.is_active(context.data):
return return

View file

@ -9,6 +9,8 @@ class ValidateLayersGroup(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder order = pyblish.api.ValidatorOrder
families = ["renderPass"] families = ["renderPass"]
settings_category = "tvpaint"
def process(self, instance): def process(self, instance):
# Prepare layers # Prepare layers
layers_by_name = instance.context.data["layersByName"] layers_by_name = instance.context.data["layersByName"]

View file

@ -10,6 +10,8 @@ class ValidateLayersVisiblity(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder order = pyblish.api.ValidatorOrder
families = ["review", "render"] families = ["review", "render"]
settings_category = "tvpaint"
def process(self, instance): def process(self, instance):
layers = instance.data.get("layers") layers = instance.data.get("layers")
# Instance have empty layers # Instance have empty layers

View file

@ -5,7 +5,7 @@ from ayon_core.pipeline import (
PublishXmlValidationError, PublishXmlValidationError,
OptionalPyblishPluginMixin, OptionalPyblishPluginMixin,
) )
from ayon_core.hosts.tvpaint.api.lib import execute_george from ayon_tvpaint.api.lib import execute_george
class ValidateMarksRepair(pyblish.api.Action): class ValidateMarksRepair(pyblish.api.Action):
@ -41,6 +41,8 @@ class ValidateMarks(
optional = True optional = True
actions = [ValidateMarksRepair] actions = [ValidateMarksRepair]
settings_category = "tvpaint"
@staticmethod @staticmethod
def get_expected_data(context): def get_expected_data(context):
scene_mark_in = context.data["sceneMarkIn"] scene_mark_in = context.data["sceneMarkIn"]

View file

@ -9,6 +9,8 @@ class ValidateMissingLayers(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder order = pyblish.api.ValidatorOrder
families = ["renderPass"] families = ["renderPass"]
settings_category = "tvpaint"
def process(self, instance): def process(self, instance):
# Prepare layers # Prepare layers
layers_by_name = instance.context.data["layersByName"] layers_by_name = instance.context.data["layersByName"]

View file

@ -12,6 +12,8 @@ class ValidateRenderLayerGroups(pyblish.api.ContextPlugin):
label = "Validate Render Layers Group" label = "Validate Render Layers Group"
order = pyblish.api.ValidatorOrder + 0.1 order = pyblish.api.ValidatorOrder + 0.1
settings_category = "tvpaint"
def process(self, context): def process(self, context):
# Prepare layers # Prepare layers
render_layers_by_group_id = collections.defaultdict(list) render_layers_by_group_id = collections.defaultdict(list)

View file

@ -13,6 +13,8 @@ class ValidateLayersGroup(pyblish.api.InstancePlugin):
order = pyblish.api.ValidatorOrder + 0.1 order = pyblish.api.ValidatorOrder + 0.1
families = ["renderPass"] families = ["renderPass"]
settings_category = "tvpaint"
def process(self, instance): def process(self, instance):
# Prepare layers # Prepare layers
layers_data = instance.context.data["layersData"] layers_data = instance.context.data["layersData"]

View file

@ -16,6 +16,8 @@ class ValidateProjectSettings(
label = "Validate Scene Settings" label = "Validate Scene Settings"
order = pyblish.api.ValidatorOrder order = pyblish.api.ValidatorOrder
settings_category = "tvpaint"
optional = True optional = True
def process(self, context): def process(self, context):

View file

@ -3,7 +3,7 @@ from ayon_core.pipeline import (
PublishXmlValidationError, PublishXmlValidationError,
OptionalPyblishPluginMixin, OptionalPyblishPluginMixin,
) )
from ayon_core.hosts.tvpaint.api.lib import execute_george from ayon_tvpaint.api.lib import execute_george
class RepairStartFrame(pyblish.api.Action): class RepairStartFrame(pyblish.api.Action):
@ -27,6 +27,8 @@ class ValidateStartFrame(
order = pyblish.api.ValidatorOrder order = pyblish.api.ValidatorOrder
hosts = ["tvpaint"] hosts = ["tvpaint"]
actions = [RepairStartFrame] actions = [RepairStartFrame]
settings_category = "tvpaint"
optional = True optional = True
def process(self, context): def process(self, context):

View file

@ -31,6 +31,8 @@ class ValidateWorkfileMetadata(pyblish.api.ContextPlugin):
actions = [ValidateWorkfileMetadataRepair] actions = [ValidateWorkfileMetadataRepair]
settings_category = "tvpaint"
required_keys = {"project_name", "folder_path", "task_name"} required_keys = {"project_name", "folder_path", "task_name"}
def process(self, context): def process(self, context):

View file

@ -12,6 +12,8 @@ class ValidateWorkfileProjectName(pyblish.api.ContextPlugin):
label = "Validate Workfile Project Name" label = "Validate Workfile Project Name"
order = pyblish.api.ValidatorOrder order = pyblish.api.ValidatorOrder
settings_category = "tvpaint"
def process(self, context): def process(self, context):
workfile_context = context.data.get("workfile_context") workfile_context = context.data.get("workfile_context")
# If workfile context is missing than project is matching to # If workfile context is missing than project is matching to

View file

@ -5,11 +5,11 @@ import tempfile
import shutil import shutil
import asyncio import asyncio
from ayon_core.hosts.tvpaint.api.communication_server import ( from ayon_tvpaint.api.communication_server import (
BaseCommunicator, BaseCommunicator,
CommunicationWrapper CommunicationWrapper
) )
from openpype_modules.job_queue.job_workers import WorkerJobsConnection from ayon_core.modules.job_queue.job_workers import WorkerJobsConnection
from .worker_job import ProcessTVPaintCommands from .worker_job import ProcessTVPaintCommands

View file

@ -256,7 +256,7 @@ class CollectSceneData(BaseCommand):
name = "collect_scene_data" name = "collect_scene_data"
def execute(self): def execute(self):
from ayon_core.hosts.tvpaint.api.lib import ( from ayon_tvpaint.api.lib import (
get_layers_data, get_layers_data,
get_groups_data, get_groups_data,
get_layers_pre_post_behavior, get_layers_pre_post_behavior,

View file

@ -1,3 +1,9 @@
name = "tvpaint" name = "tvpaint"
title = "TVPaint" title = "TVPaint"
version = "0.1.2" version = "0.2.0"
client_dir = "ayon_tvpaint"
ayon_required_addons = {
"core": ">0.3.2",
}
ayon_compatible_addons = {}