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
MOVED_ADDON_MILESTONE_VERSIONS = {
"applications": VersionInfo(0, 2, 0),
"clockify": VersionInfo(0, 2, 0),
"tvpaint": VersionInfo(0, 2, 0),
}
# Inherit from `object` for Python 2 hosts

View file

@ -365,3 +365,62 @@ def maintained_time():
yield
finally:
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
VALID_EXTENSIONS = [".blend", ".json", ".abc", ".fbx"]
VALID_EXTENSIONS = [".blend", ".json", ".abc", ".fbx",
".usd", ".usdc", ".usda"]
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:
At least for now it only supports Alembic files.
"""
product_types = {"model", "pointcache", "animation"}
representations = {"abc"}
product_types = {"model", "pointcache", "animation", "usd"}
representations = {"abc", "usd"}
label = "Load Alembic"
label = "Load Cache"
icon = "code-fork"
color = "orange"
@ -53,10 +53,21 @@ class CacheModelLoader(plugin.AssetLoader):
plugin.deselect_all()
relative = bpy.context.preferences.filepaths.use_relative_paths
bpy.ops.wm.alembic_import(
filepath=libpath,
relative_path=relative
)
if any(libpath.lower().endswith(ext)
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()

View file

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

View file

@ -10,7 +10,7 @@ class CollectChunkSize(pyblish.api.InstancePlugin,
order = pyblish.api.CollectorOrder + 0.05
families = ["ass", "pointcache",
"vdbcache", "mantraifd",
"redshiftproxy"]
"redshiftproxy", "model"]
hosts = ["houdini"]
targets = ["local", "remote"]
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.
"""
import pyblish.api
class CollectPointcacheType(pyblish.api.InstancePlugin):
"""Collect data type for pointcache instance."""
"""Collect data type for different instances."""
order = pyblish.api.CollectorOrder
hosts = ["houdini"]
families = ["pointcache"]
label = "Collect type of pointcache"
families = ["pointcache", "model"]
label = "Collect instances types"
def process(self, instance):
if instance.data["creator_identifier"] == "io.openpype.creators.houdini.bgeo": # noqa: E501
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"]

View file

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

View file

@ -15,7 +15,8 @@ class CollectOutputSOPPath(pyblish.api.InstancePlugin):
"usd",
"usdrender",
"redshiftproxy",
"staticMesh"
"staticMesh",
"model"
]
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.
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"]
label = "Validate Mesh is Static"
order = ValidateContentsOrder + 0.1

View file

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

View file

@ -22,7 +22,7 @@ class ValidateSopOutputNode(pyblish.api.InstancePlugin):
"""
order = pyblish.api.ValidatorOrder
families = ["pointcache", "vdbcache"]
families = ["pointcache", "vdbcache", "model"]
hosts = ["houdini"]
label = "Validate Output Node (SOP)"
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)
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):
def __init__(self, controller, parent):
super(ActionsWidget, self).__init__(parent)
@ -316,10 +344,7 @@ class ActionsWidget(QtWidgets.QWidget):
model = ActionsQtModel(controller)
proxy_model = QtCore.QSortFilterProxyModel()
proxy_model.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
proxy_model.setSortRole(ACTION_SORT_ROLE)
proxy_model = ActionsProxyModel()
proxy_model.setSourceModel(model)
view.setModel(proxy_model)

View file

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""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"
title = "Core"
version = "0.3.2-dev.1"
version = "0.3.3-dev.1"
client_dir = "ayon_core"

View file

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

View file

@ -151,6 +151,10 @@ class PublishPluginsModel(BaseSettingsModel):
default_factory=ExtractPlayblastModel,
title="Extract Playblast"
)
ExtractModelUSD: ValidatePluginModel = SettingsField(
default_factory=ValidatePluginModel,
title="Extract Model USD"
)
DEFAULT_BLENDER_PUBLISH_SETTINGS = {
@ -348,5 +352,10 @@ DEFAULT_BLENDER_PUBLISH_SETTINGS = {
},
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 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
class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths):
class ClockifyAddon(AYONAddon, ITrayAddon, IPluginPaths):
name = "clockify"
def initialize(self, studio_settings):
@ -31,7 +31,7 @@ class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths):
# TimersManager attributes
# - set `timers_manager_connector` only in `tray_init`
self.timers_manager_connector = None
self._timers_manager_module = None
self._timer_manager_addon = None
@property
def clockify_api(self):
@ -87,7 +87,7 @@ class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths):
return {"actions": [actions_path]}
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 {
"user": [CLOCKIFY_FTRACK_USER_PATH],
"server": [CLOCKIFY_FTRACK_SERVER_PATH],
@ -206,19 +206,19 @@ class ClockifyModule(AYONAddon, ITrayModule, IPluginPaths):
self.action_stop_timer.setVisible(self.bool_timer_run)
# --- TimersManager connection methods ---
def register_timers_manager(self, timer_manager_module):
def register_timers_manager(self, timer_manager_addon):
"""Store TimersManager for future use."""
self._timers_manager_module = timer_manager_module
self._timer_manager_addon = timer_manager_addon
def timer_started(self, data):
"""Tell TimersManager that timer started."""
if self._timers_manager_module is not None:
self._timers_manager_module.timer_started(self.id, data)
if self._timer_manager_addon is not None:
self._timer_manager_addon.timer_started(self.id, data)
def timer_stopped(self):
"""Tell TimersManager that timer stopped."""
if self._timers_manager_module is not None:
self._timers_manager_module.timer_stopped(self.id)
if self._timer_manager_addon is not None:
self._timer_manager_addon.timer_stopped(self.id)
def stop_timer(self):
"""Called from TimersManager to stop timer."""

View file

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

View file

@ -1,7 +1,9 @@
import os
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):

View file

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

View file

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

View file

@ -1,6 +1,6 @@
import ayon_api
from openpype_modules.clockify.clockify_api import ClockifyAPI
from ayon_clockify.clockify_api import ClockifyAPI
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"
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 -*-
"""Package declaring AYON core addon version."""
"""Package declaring AYON addon '{}' version."""
__version__ = "{}"
'''
@ -183,6 +183,7 @@ def create_addon_zip(
def prepare_client_code(
addon_name: str,
addon_dir: Path,
addon_output_dir: Path,
addon_version: str
@ -211,7 +212,9 @@ def prepare_client_code(
version_path = subpath / "version.py"
if version_path.exists():
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"
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
)
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:
create_addon_zip(

View file

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

View file

@ -22,7 +22,7 @@ from aiohttp_json_rpc.protocol import (
from aiohttp_json_rpc.exceptions import RpcError
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.setLevel(logging.DEBUG)

View file

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

View file

@ -7,8 +7,9 @@ import requests
import ayon_api
import pyblish.api
from ayon_tvpaint import TVPAINT_ROOT_DIR
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.lib import register_event_callback
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
SHARED_DATA_KEY = "openpype.tvpaint.instances"
SHARED_DATA_KEY = "ayon.tvpaint.instances"
class TVPaintCreatorCommon:
@ -89,6 +89,8 @@ class TVPaintCreatorCommon:
class TVPaintCreator(Creator, TVPaintCreatorCommon):
settings_category = "tvpaint"
def collect_instances(self):
self._collect_create_instances()
@ -140,6 +142,8 @@ class TVPaintCreator(Creator, TVPaintCreatorCommon):
class TVPaintAutoCreator(AutoCreator, TVPaintCreatorCommon):
settings_category = "tvpaint"
def collect_instances(self):
self._collect_create_instances()
@ -152,6 +156,7 @@ class TVPaintAutoCreator(AutoCreator, TVPaintCreatorCommon):
class Loader(LoaderPlugin):
hosts = ["tvpaint"]
settings_category = "tvpaint"
@staticmethod
def get_members_from_container(container):

View file

@ -37,6 +37,6 @@ class TvpaintPrelaunchHook(PreLaunchHook):
self.launch_context.launch_args.extend(remainders)
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()

View file

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

View file

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

View file

@ -1,7 +1,7 @@
import ayon_api
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):

View file

@ -1,7 +1,7 @@
import ayon_api
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):

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -12,6 +12,8 @@ class ValidateRenderLayerGroups(pyblish.api.ContextPlugin):
label = "Validate Render Layers Group"
order = pyblish.api.ValidatorOrder + 0.1
settings_category = "tvpaint"
def process(self, context):
# Prepare layers
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
families = ["renderPass"]
settings_category = "tvpaint"
def process(self, instance):
# Prepare layers
layers_data = instance.context.data["layersData"]

View file

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

View file

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

View file

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

View file

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

View file

@ -5,11 +5,11 @@ import tempfile
import shutil
import asyncio
from ayon_core.hosts.tvpaint.api.communication_server import (
from ayon_tvpaint.api.communication_server import (
BaseCommunicator,
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

View file

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

View file

@ -1,3 +1,9 @@
name = "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 = {}