Merge branch 'develop' into enhancement/OP-7071_Validate-Attributes

This commit is contained in:
Kayla Man 2023-10-27 16:33:08 +08:00
commit 81fbead3be
27 changed files with 1473 additions and 704 deletions

View file

@ -35,6 +35,8 @@ body:
label: Version
description: What version are you running? Look to OpenPype Tray
options:
- 3.17.4
- 3.17.4-nightly.2
- 3.17.4-nightly.1
- 3.17.3
- 3.17.3-nightly.2
@ -133,8 +135,6 @@ body:
- 3.15.1-nightly.2
- 3.15.1-nightly.1
- 3.15.0
- 3.15.0-nightly.1
- 3.14.11-nightly.4
validations:
required: true
- type: dropdown

View file

@ -1,6 +1,274 @@
# Changelog
## [3.17.4](https://github.com/ynput/OpenPype/tree/3.17.4)
[Full Changelog](https://github.com/ynput/OpenPype/compare/3.17.3...3.17.4)
### **🆕 New features**
<details>
<summary>Add Support for Husk-AYON Integration <a href="https://github.com/ynput/OpenPype/pull/5816">#5816</a></summary>
This draft pull request introduces support for integrating Husk with AYON within the OpenPype repository.
___
</details>
<details>
<summary>Push to project tool: Prepare push to project tool for AYON <a href="https://github.com/ynput/OpenPype/pull/5770">#5770</a></summary>
Cloned Push to project tool for AYON and modified it.
___
</details>
### **🚀 Enhancements**
<details>
<summary>Max: tycache family support <a href="https://github.com/ynput/OpenPype/pull/5624">#5624</a></summary>
Tycache family supports for Tyflow Plugin in Max
___
</details>
<details>
<summary>Unreal: Changed behaviour for updating assets <a href="https://github.com/ynput/OpenPype/pull/5670">#5670</a></summary>
Changed how assets are updated in Unreal.
___
</details>
<details>
<summary>Unreal: Improved error reporting for Sequence Frame Validator <a href="https://github.com/ynput/OpenPype/pull/5730">#5730</a></summary>
Improved error reporting for Sequence Frame Validator.
___
</details>
<details>
<summary>Max: Setting tweaks on Review Family <a href="https://github.com/ynput/OpenPype/pull/5744">#5744</a></summary>
- Bug fix of not being able to publish the preferred visual style when creating preview animation
- Exposes the parameters after creating instance
- Add the Quality settings and viewport texture settings for preview animation
- add use selection for create review
___
</details>
<details>
<summary>Max: Add families with frame range extractions back to the frame range validator <a href="https://github.com/ynput/OpenPype/pull/5757">#5757</a></summary>
In 3dsMax, there are some instances which exports the files in frame range but not being added to the optional frame range validator. In this PR, these instances would have the optional frame range validators to allow users to check if frame range aligns with the context data from DB.The following families have been added to have optional frame range validator:
- maxrender
- review
- camera
- redshift proxy
- pointcache
- point cloud(tyFlow PRT)
___
</details>
<details>
<summary>TimersManager: Use available data to get context info <a href="https://github.com/ynput/OpenPype/pull/5804">#5804</a></summary>
Get context information from pyblish context data instead of using `legacy_io`.
___
</details>
<details>
<summary>Chore: Removed unused variable from `AbstractCollectRender` <a href="https://github.com/ynput/OpenPype/pull/5805">#5805</a></summary>
Removed unused `_asset` variable from `RenderInstance`.
___
</details>
### **🐛 Bug fixes**
<details>
<summary>Bugfix/houdini: wrong frame calculation with handles <a href="https://github.com/ynput/OpenPype/pull/5698">#5698</a></summary>
This PR make collect plugins to consider `handleStart` and `handleEnd` when collecting frame range it affects three parts:
- get frame range in collect plugins
- expected file in render plugins
- submit houdini job deadline plugin
___
</details>
<details>
<summary>Nuke: ayon server settings improvements <a href="https://github.com/ynput/OpenPype/pull/5746">#5746</a></summary>
Nuke settings were not aligned with OpenPype settings. Also labels needed to be improved.
___
</details>
<details>
<summary>Blender: Fix pointcache family and fix alembic extractor <a href="https://github.com/ynput/OpenPype/pull/5747">#5747</a></summary>
Fixed `pointcache` family and fixed behaviour of the alembic extractor.
___
</details>
<details>
<summary>AYON: Remove 'shotgun_api3' from dependencies <a href="https://github.com/ynput/OpenPype/pull/5803">#5803</a></summary>
Removed `shotgun_api3` dependency from openpype dependencies for AYON launcher. The dependency is already defined in shotgrid addon and change of version causes clashes.
___
</details>
<details>
<summary>Chore: Fix typo in filename <a href="https://github.com/ynput/OpenPype/pull/5807">#5807</a></summary>
Move content of `contants.py` into `constants.py`.
___
</details>
<details>
<summary>Chore: Create context respects instance changes <a href="https://github.com/ynput/OpenPype/pull/5809">#5809</a></summary>
Fix issue with unrespected change propagation in `CreateContext`. All successfully saved instances are marked as saved so they have no changes. Origin data of an instance are explicitly not handled directly by the object but by the attribute wrappers.
___
</details>
<details>
<summary>Blender: Fix tools handling in AYON mode <a href="https://github.com/ynput/OpenPype/pull/5811">#5811</a></summary>
Skip logic in `before_window_show` in blender when in AYON mode. Most of the stuff called there happes on show automatically.
___
</details>
<details>
<summary>Blender: Include Grease Pencil in review and thumbnails <a href="https://github.com/ynput/OpenPype/pull/5812">#5812</a></summary>
Include Grease Pencil in review and thumbnails.
___
</details>
<details>
<summary>Workfiles tool AYON: Fix double click of workfile <a href="https://github.com/ynput/OpenPype/pull/5813">#5813</a></summary>
Fix double click on workfiles in workfiles tool to open the file.
___
</details>
<details>
<summary>Webpublisher: removal of usage of no_of_frames in error message <a href="https://github.com/ynput/OpenPype/pull/5819">#5819</a></summary>
If it throws exception, `no_of_frames` value wont be available, so it doesn't make sense to log it.
___
</details>
<details>
<summary>Attribute Defs: Hide multivalue widget in Number by default <a href="https://github.com/ynput/OpenPype/pull/5821">#5821</a></summary>
Fixed default look of `NumberAttrWidget` by hiding its multiselection widget.
___
</details>
### **Merged pull requests**
<details>
<summary>Corrected a typo in Readme.md (Top -> To) <a href="https://github.com/ynput/OpenPype/pull/5800">#5800</a></summary>
___
</details>
<details>
<summary>Photoshop: Removed redundant copy of extension.zxp <a href="https://github.com/ynput/OpenPype/pull/5802">#5802</a></summary>
`extension.zxp` shouldn't be inside of extension folder.
___
</details>
## [3.17.3](https://github.com/ynput/OpenPype/tree/3.17.3)

View file

@ -204,8 +204,6 @@ class LoadImage(load.LoaderPlugin):
last = first = int(frame_number)
# Set the global in to the start frame of the sequence
read_name = self._get_node_name(representation)
node["name"].setValue(read_name)
node["file"].setValue(file)
node["origfirst"].setValue(first)
node["first"].setValue(first)

View file

@ -13,8 +13,10 @@ from openpype.client import get_asset_by_name, get_assets
from openpype.pipeline import (
register_loader_plugin_path,
register_creator_plugin_path,
register_inventory_action_path,
deregister_loader_plugin_path,
deregister_creator_plugin_path,
deregister_inventory_action_path,
AYON_CONTAINER_ID,
legacy_io,
)
@ -28,6 +30,7 @@ import unreal # noqa
logger = logging.getLogger("openpype.hosts.unreal")
AYON_CONTAINERS = "AyonContainers"
AYON_ASSET_DIR = "/Game/Ayon/Assets"
CONTEXT_CONTAINER = "Ayon/context.json"
UNREAL_VERSION = semver.VersionInfo(
*os.getenv("AYON_UNREAL_VERSION").split(".")
@ -127,6 +130,7 @@ def install():
pyblish.api.register_plugin_path(str(PUBLISH_PATH))
register_loader_plugin_path(str(LOAD_PATH))
register_creator_plugin_path(str(CREATE_PATH))
register_inventory_action_path(str(INVENTORY_PATH))
_register_callbacks()
_register_events()
@ -136,6 +140,7 @@ def uninstall():
pyblish.api.deregister_plugin_path(str(PUBLISH_PATH))
deregister_loader_plugin_path(str(LOAD_PATH))
deregister_creator_plugin_path(str(CREATE_PATH))
deregister_inventory_action_path(str(INVENTORY_PATH))
def _register_callbacks():
@ -649,6 +654,141 @@ def generate_sequence(h, h_dir):
return sequence, (min_frame, max_frame)
def _get_comps_and_assets(
component_class, asset_class, old_assets, new_assets, selected
):
eas = unreal.get_editor_subsystem(unreal.EditorActorSubsystem)
components = []
if selected:
sel_actors = eas.get_selected_level_actors()
for actor in sel_actors:
comps = actor.get_components_by_class(component_class)
components.extend(comps)
else:
comps = eas.get_all_level_actors_components()
components = [
c for c in comps if isinstance(c, component_class)
]
# Get all the static meshes among the old assets in a dictionary with
# the name as key
selected_old_assets = {}
for a in old_assets:
asset = unreal.EditorAssetLibrary.load_asset(a)
if isinstance(asset, asset_class):
selected_old_assets[asset.get_name()] = asset
# Get all the static meshes among the new assets in a dictionary with
# the name as key
selected_new_assets = {}
for a in new_assets:
asset = unreal.EditorAssetLibrary.load_asset(a)
if isinstance(asset, asset_class):
selected_new_assets[asset.get_name()] = asset
return components, selected_old_assets, selected_new_assets
def replace_static_mesh_actors(old_assets, new_assets, selected):
smes = unreal.get_editor_subsystem(unreal.StaticMeshEditorSubsystem)
static_mesh_comps, old_meshes, new_meshes = _get_comps_and_assets(
unreal.StaticMeshComponent,
unreal.StaticMesh,
old_assets,
new_assets,
selected
)
for old_name, old_mesh in old_meshes.items():
new_mesh = new_meshes.get(old_name)
if not new_mesh:
continue
smes.replace_mesh_components_meshes(
static_mesh_comps, old_mesh, new_mesh)
def replace_skeletal_mesh_actors(old_assets, new_assets, selected):
skeletal_mesh_comps, old_meshes, new_meshes = _get_comps_and_assets(
unreal.SkeletalMeshComponent,
unreal.SkeletalMesh,
old_assets,
new_assets,
selected
)
for old_name, old_mesh in old_meshes.items():
new_mesh = new_meshes.get(old_name)
if not new_mesh:
continue
for comp in skeletal_mesh_comps:
if comp.get_skeletal_mesh_asset() == old_mesh:
comp.set_skeletal_mesh_asset(new_mesh)
def replace_geometry_cache_actors(old_assets, new_assets, selected):
geometry_cache_comps, old_caches, new_caches = _get_comps_and_assets(
unreal.GeometryCacheComponent,
unreal.GeometryCache,
old_assets,
new_assets,
selected
)
for old_name, old_mesh in old_caches.items():
new_mesh = new_caches.get(old_name)
if not new_mesh:
continue
for comp in geometry_cache_comps:
if comp.get_editor_property("geometry_cache") == old_mesh:
comp.set_geometry_cache(new_mesh)
def delete_asset_if_unused(container, asset_content):
ar = unreal.AssetRegistryHelpers.get_asset_registry()
references = set()
for asset_path in asset_content:
asset = ar.get_asset_by_object_path(asset_path)
refs = ar.get_referencers(
asset.package_name,
unreal.AssetRegistryDependencyOptions(
include_soft_package_references=False,
include_hard_package_references=True,
include_searchable_names=False,
include_soft_management_references=False,
include_hard_management_references=False
))
if not refs:
continue
references = references.union(set(refs))
# Filter out references that are in the Temp folder
cleaned_references = {
ref for ref in references if not str(ref).startswith("/Temp/")}
# Check which of the references are Levels
for ref in cleaned_references:
loaded_asset = unreal.EditorAssetLibrary.load_asset(ref)
if isinstance(loaded_asset, unreal.World):
# If there is at least a level, we can stop, we don't want to
# delete the container
return
unreal.log("Previous version unused, deleting...")
# No levels, delete the asset
unreal.EditorAssetLibrary.delete_directory(container["namespace"])
@contextmanager
def maintained_selection():
"""Stub to be either implemented or replaced.

View file

@ -0,0 +1,66 @@
import unreal
from openpype.hosts.unreal.api.tools_ui import qt_app_context
from openpype.hosts.unreal.api.pipeline import delete_asset_if_unused
from openpype.pipeline import InventoryAction
class DeleteUnusedAssets(InventoryAction):
"""Delete all the assets that are not used in any level.
"""
label = "Delete Unused Assets"
icon = "trash"
color = "red"
order = 1
dialog = None
def _delete_unused_assets(self, containers):
allowed_families = ["model", "rig"]
for container in containers:
container_dir = container.get("namespace")
if container.get("family") not in allowed_families:
unreal.log_warning(
f"Container {container_dir} is not supported.")
continue
asset_content = unreal.EditorAssetLibrary.list_assets(
container_dir, recursive=True, include_folder=False
)
delete_asset_if_unused(container, asset_content)
def _show_confirmation_dialog(self, containers):
from qtpy import QtCore
from openpype.widgets import popup
from openpype.style import load_stylesheet
dialog = popup.Popup()
dialog.setWindowFlags(
QtCore.Qt.Window
| QtCore.Qt.WindowStaysOnTopHint
)
dialog.setFocusPolicy(QtCore.Qt.StrongFocus)
dialog.setWindowTitle("Delete all unused assets")
dialog.setMessage(
"You are about to delete all the assets in the project that \n"
"are not used in any level. Are you sure you want to continue?"
)
dialog.setButtonText("Delete")
dialog.on_clicked.connect(
lambda: self._delete_unused_assets(containers)
)
dialog.show()
dialog.raise_()
dialog.activateWindow()
dialog.setStyleSheet(load_stylesheet())
self.dialog = dialog
def process(self, containers):
with qt_app_context():
self._show_confirmation_dialog(containers)

View file

@ -0,0 +1,84 @@
import unreal
from openpype.hosts.unreal.api.pipeline import (
ls,
replace_static_mesh_actors,
replace_skeletal_mesh_actors,
replace_geometry_cache_actors,
)
from openpype.pipeline import InventoryAction
def update_assets(containers, selected):
allowed_families = ["model", "rig"]
# Get all the containers in the Unreal Project
all_containers = ls()
for container in containers:
container_dir = container.get("namespace")
if container.get("family") not in allowed_families:
unreal.log_warning(
f"Container {container_dir} is not supported.")
continue
# Get all containers with same asset_name but different objectName.
# These are the containers that need to be updated in the level.
sa_containers = [
i
for i in all_containers
if (
i.get("asset_name") == container.get("asset_name") and
i.get("objectName") != container.get("objectName")
)
]
asset_content = unreal.EditorAssetLibrary.list_assets(
container_dir, recursive=True, include_folder=False
)
# Update all actors in level
for sa_cont in sa_containers:
sa_dir = sa_cont.get("namespace")
old_content = unreal.EditorAssetLibrary.list_assets(
sa_dir, recursive=True, include_folder=False
)
if container.get("family") == "rig":
replace_skeletal_mesh_actors(
old_content, asset_content, selected)
replace_static_mesh_actors(
old_content, asset_content, selected)
elif container.get("family") == "model":
if container.get("loader") == "PointCacheAlembicLoader":
replace_geometry_cache_actors(
old_content, asset_content, selected)
else:
replace_static_mesh_actors(
old_content, asset_content, selected)
unreal.EditorLevelLibrary.save_current_level()
class UpdateAllActors(InventoryAction):
"""Update all the Actors in the current level to the version of the asset
selected in the scene manager.
"""
label = "Replace all Actors in level to this version"
icon = "arrow-up"
def process(self, containers):
update_assets(containers, False)
class UpdateSelectedActors(InventoryAction):
"""Update only the selected Actors in the current level to the version
of the asset selected in the scene manager.
"""
label = "Replace selected Actors in level to this version"
icon = "arrow-up"
def process(self, containers):
update_assets(containers, True)

View file

@ -69,7 +69,7 @@ class AnimationAlembicLoader(plugin.Loader):
"""
# Create directory for asset and ayon container
root = "/Game/Ayon/Assets"
root = unreal_pipeline.AYON_ASSET_DIR
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:

View file

@ -7,7 +7,11 @@ from openpype.pipeline import (
AYON_CONTAINER_ID
)
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
from openpype.hosts.unreal.api.pipeline import (
AYON_ASSET_DIR,
create_container,
imprint,
)
import unreal # noqa
@ -21,8 +25,11 @@ class PointCacheAlembicLoader(plugin.Loader):
icon = "cube"
color = "orange"
root = AYON_ASSET_DIR
@staticmethod
def get_task(
self, filename, asset_dir, asset_name, replace,
filename, asset_dir, asset_name, replace,
frame_start=None, frame_end=None
):
task = unreal.AssetImportTask()
@ -38,8 +45,6 @@ class PointCacheAlembicLoader(plugin.Loader):
task.set_editor_property('automated', True)
task.set_editor_property('save', True)
# set import options here
# Unreal 4.24 ignores the settings. It works with Unreal 4.26
options.set_editor_property(
'import_type', unreal.AlembicImportType.GEOMETRY_CACHE)
@ -64,13 +69,42 @@ class PointCacheAlembicLoader(plugin.Loader):
return task
def load(self, context, name, namespace, data):
"""Load and containerise representation into Content Browser.
def import_and_containerize(
self, filepath, asset_dir, asset_name, container_name,
frame_start, frame_end
):
unreal.EditorAssetLibrary.make_directory(asset_dir)
This is two step process. First, import FBX to temporary path and
then call `containerise()` on it - this moves all content to new
directory and then it will create AssetContainer there and imprint it
with metadata. This will mark this path as container.
task = self.get_task(
filepath, asset_dir, asset_name, False, frame_start, frame_end)
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
# Create Asset Container
create_container(container=container_name, path=asset_dir)
def imprint(
self, asset, asset_dir, container_name, asset_name, representation,
frame_start, frame_end
):
data = {
"schema": "ayon:container-2.0",
"id": AYON_CONTAINER_ID,
"asset": asset,
"namespace": asset_dir,
"container_name": container_name,
"asset_name": asset_name,
"loader": str(self.__class__.__name__),
"representation": representation["_id"],
"parent": representation["parent"],
"family": representation["context"]["family"],
"frame_start": frame_start,
"frame_end": frame_end
}
imprint(f"{asset_dir}/{container_name}", data)
def load(self, context, name, namespace, options):
"""Load and containerise representation into Content Browser.
Args:
context (dict): application context
@ -79,30 +113,28 @@ class PointCacheAlembicLoader(plugin.Loader):
This is not passed here, so namespace is set
by `containerise()` because only then we know
real path.
data (dict): Those would be data to be imprinted. This is not used
now, data are imprinted by `containerise()`.
data (dict): Those would be data to be imprinted.
Returns:
list(str): list of container content
"""
# Create directory for asset and Ayon container
root = "/Game/Ayon/Assets"
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:
asset_name = "{}_{}".format(asset, name)
asset_name = f"{asset}_{name}" if asset else f"{name}"
version = context.get('version')
# Check if version is hero version and use different name
if not version.get("name") and version.get('type') == "hero_version":
name_version = f"{name}_hero"
else:
asset_name = "{}".format(name)
name_version = f"{name}_v{version.get('name'):03d}"
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
"{}/{}/{}".format(root, asset, name), suffix="")
f"{self.root}/{asset}/{name_version}", suffix="")
container_name += suffix
unreal.EditorAssetLibrary.make_directory(asset_dir)
frame_start = context.get('asset').get('data').get('frameStart')
frame_end = context.get('asset').get('data').get('frameEnd')
@ -111,30 +143,16 @@ class PointCacheAlembicLoader(plugin.Loader):
if frame_start == frame_end:
frame_end += 1
path = self.filepath_from_context(context)
task = self.get_task(
path, asset_dir, asset_name, False, frame_start, frame_end)
if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir):
path = self.filepath_from_context(context)
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) # noqa: E501
self.import_and_containerize(
path, asset_dir, asset_name, container_name,
frame_start, frame_end)
# Create Asset Container
unreal_pipeline.create_container(
container=container_name, path=asset_dir)
data = {
"schema": "ayon:container-2.0",
"id": AYON_CONTAINER_ID,
"asset": asset,
"namespace": asset_dir,
"container_name": container_name,
"asset_name": asset_name,
"loader": str(self.__class__.__name__),
"representation": context["representation"]["_id"],
"parent": context["representation"]["parent"],
"family": context["representation"]["context"]["family"]
}
unreal_pipeline.imprint(
"{}/{}".format(asset_dir, container_name), data)
self.imprint(
asset, asset_dir, container_name, asset_name,
context["representation"], frame_start, frame_end)
asset_content = unreal.EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=True
@ -146,27 +164,43 @@ class PointCacheAlembicLoader(plugin.Loader):
return asset_content
def update(self, container, representation):
name = container["asset_name"]
source_path = get_representation_path(representation)
destination_path = container["namespace"]
representation["context"]
context = representation.get("context", {})
task = self.get_task(source_path, destination_path, name, False)
# do import fbx and replace existing data
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
unreal.log_warning(context)
container_path = "{}/{}".format(container["namespace"],
container["objectName"])
# update metadata
unreal_pipeline.imprint(
container_path,
{
"representation": str(representation["_id"]),
"parent": str(representation["parent"])
})
if not context:
raise RuntimeError("No context found in representation")
# Create directory for asset and Ayon container
asset = context.get('asset')
name = context.get('subset')
suffix = "_CON"
asset_name = f"{asset}_{name}" if asset else f"{name}"
version = context.get('version')
# Check if version is hero version and use different name
name_version = f"{name}_v{version:03d}" if version else f"{name}_hero"
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
f"{self.root}/{asset}/{name_version}", suffix="")
container_name += suffix
frame_start = int(container.get("frame_start"))
frame_end = int(container.get("frame_end"))
if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir):
path = get_representation_path(representation)
self.import_and_containerize(
path, asset_dir, asset_name, container_name,
frame_start, frame_end)
self.imprint(
asset, asset_dir, container_name, asset_name, representation,
frame_start, frame_end)
asset_content = unreal.EditorAssetLibrary.list_assets(
destination_path, recursive=True, include_folder=True
asset_dir, recursive=True, include_folder=False
)
for a in asset_content:

View file

@ -7,7 +7,11 @@ from openpype.pipeline import (
AYON_CONTAINER_ID
)
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
from openpype.hosts.unreal.api.pipeline import (
AYON_ASSET_DIR,
create_container,
imprint,
)
import unreal # noqa
@ -20,10 +24,12 @@ class SkeletalMeshAlembicLoader(plugin.Loader):
icon = "cube"
color = "orange"
def get_task(self, filename, asset_dir, asset_name, replace):
root = AYON_ASSET_DIR
@staticmethod
def get_task(filename, asset_dir, asset_name, replace, default_conversion):
task = unreal.AssetImportTask()
options = unreal.AbcImportSettings()
sm_settings = unreal.AbcStaticMeshSettings()
conversion_settings = unreal.AbcConversionSettings(
preset=unreal.AbcConversionPreset.CUSTOM,
flip_u=False, flip_v=False,
@ -37,72 +43,38 @@ class SkeletalMeshAlembicLoader(plugin.Loader):
task.set_editor_property('automated', True)
task.set_editor_property('save', True)
# set import options here
# Unreal 4.24 ignores the settings. It works with Unreal 4.26
options.set_editor_property(
'import_type', unreal.AlembicImportType.SKELETAL)
options.static_mesh_settings = sm_settings
options.conversion_settings = conversion_settings
if not default_conversion:
conversion_settings = unreal.AbcConversionSettings(
preset=unreal.AbcConversionPreset.CUSTOM,
flip_u=False, flip_v=False,
rotation=[0.0, 0.0, 0.0],
scale=[1.0, 1.0, 1.0])
options.conversion_settings = conversion_settings
task.options = options
return task
def load(self, context, name, namespace, data):
"""Load and containerise representation into Content Browser.
def import_and_containerize(
self, filepath, asset_dir, asset_name, container_name,
default_conversion=False
):
unreal.EditorAssetLibrary.make_directory(asset_dir)
This is two step process. First, import FBX to temporary path and
then call `containerise()` on it - this moves all content to new
directory and then it will create AssetContainer there and imprint it
with metadata. This will mark this path as container.
task = self.get_task(
filepath, asset_dir, asset_name, False, default_conversion)
Args:
context (dict): application context
name (str): subset name
namespace (str): in Unreal this is basically path to container.
This is not passed here, so namespace is set
by `containerise()` because only then we know
real path.
data (dict): Those would be data to be imprinted. This is not used
now, data are imprinted by `containerise()`.
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
Returns:
list(str): list of container content
"""
# Create directory for asset and ayon container
root = "/Game/Ayon/Assets"
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:
asset_name = "{}_{}".format(asset, name)
else:
asset_name = "{}".format(name)
version = context.get('version')
# Check if version is hero version and use different name
if not version.get("name") and version.get('type') == "hero_version":
name_version = f"{name}_hero"
else:
name_version = f"{name}_v{version.get('name'):03d}"
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
f"{root}/{asset}/{name_version}", suffix="")
container_name += suffix
if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir):
unreal.EditorAssetLibrary.make_directory(asset_dir)
path = self.filepath_from_context(context)
task = self.get_task(path, asset_dir, asset_name, False)
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) # noqa: E501
# Create Asset Container
unreal_pipeline.create_container(
container=container_name, path=asset_dir)
# Create Asset Container
create_container(container=container_name, path=asset_dir)
def imprint(
self, asset, asset_dir, container_name, asset_name, representation
):
data = {
"schema": "ayon:container-2.0",
"id": AYON_CONTAINER_ID,
@ -111,12 +83,57 @@ class SkeletalMeshAlembicLoader(plugin.Loader):
"container_name": container_name,
"asset_name": asset_name,
"loader": str(self.__class__.__name__),
"representation": context["representation"]["_id"],
"parent": context["representation"]["parent"],
"family": context["representation"]["context"]["family"]
"representation": representation["_id"],
"parent": representation["parent"],
"family": representation["context"]["family"]
}
unreal_pipeline.imprint(
f"{asset_dir}/{container_name}", data)
imprint(f"{asset_dir}/{container_name}", data)
def load(self, context, name, namespace, options):
"""Load and containerise representation into Content Browser.
Args:
context (dict): application context
name (str): subset name
namespace (str): in Unreal this is basically path to container.
This is not passed here, so namespace is set
by `containerise()` because only then we know
real path.
data (dict): Those would be data to be imprinted.
Returns:
list(str): list of container content
"""
# Create directory for asset and ayon container
asset = context.get('asset').get('name')
suffix = "_CON"
asset_name = f"{asset}_{name}" if asset else f"{name}"
version = context.get('version')
# Check if version is hero version and use different name
if not version.get("name") and version.get('type') == "hero_version":
name_version = f"{name}_hero"
else:
name_version = f"{name}_v{version.get('name'):03d}"
default_conversion = False
if options.get("default_conversion"):
default_conversion = options.get("default_conversion")
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
f"{self.root}/{asset}/{name_version}", suffix="")
container_name += suffix
if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir):
path = self.filepath_from_context(context)
self.import_and_containerize(path, asset_dir, asset_name,
container_name, default_conversion)
self.imprint(
asset, asset_dir, container_name, asset_name,
context["representation"])
asset_content = unreal.EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=True
@ -128,26 +145,36 @@ class SkeletalMeshAlembicLoader(plugin.Loader):
return asset_content
def update(self, container, representation):
name = container["asset_name"]
source_path = get_representation_path(representation)
destination_path = container["namespace"]
context = representation.get("context", {})
task = self.get_task(source_path, destination_path, name, True)
if not context:
raise RuntimeError("No context found in representation")
# do import fbx and replace existing data
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
container_path = "{}/{}".format(container["namespace"],
container["objectName"])
# update metadata
unreal_pipeline.imprint(
container_path,
{
"representation": str(representation["_id"]),
"parent": str(representation["parent"])
})
# Create directory for asset and Ayon container
asset = context.get('asset')
name = context.get('subset')
suffix = "_CON"
asset_name = f"{asset}_{name}" if asset else f"{name}"
version = context.get('version')
# Check if version is hero version and use different name
name_version = f"{name}_v{version:03d}" if version else f"{name}_hero"
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
f"{self.root}/{asset}/{name_version}", suffix="")
container_name += suffix
if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir):
path = get_representation_path(representation)
self.import_and_containerize(path, asset_dir, asset_name,
container_name)
self.imprint(
asset, asset_dir, container_name, asset_name, representation)
asset_content = unreal.EditorAssetLibrary.list_assets(
destination_path, recursive=True, include_folder=True
asset_dir, recursive=True, include_folder=False
)
for a in asset_content:

View file

@ -7,7 +7,11 @@ from openpype.pipeline import (
AYON_CONTAINER_ID
)
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
from openpype.hosts.unreal.api.pipeline import (
AYON_ASSET_DIR,
create_container,
imprint,
)
import unreal # noqa
@ -20,14 +24,79 @@ class SkeletalMeshFBXLoader(plugin.Loader):
icon = "cube"
color = "orange"
root = AYON_ASSET_DIR
@staticmethod
def get_task(filename, asset_dir, asset_name, replace):
task = unreal.AssetImportTask()
options = unreal.FbxImportUI()
task.set_editor_property('filename', filename)
task.set_editor_property('destination_path', asset_dir)
task.set_editor_property('destination_name', asset_name)
task.set_editor_property('replace_existing', replace)
task.set_editor_property('automated', True)
task.set_editor_property('save', True)
options.set_editor_property(
'automated_import_should_detect_type', False)
options.set_editor_property('import_as_skeletal', True)
options.set_editor_property('import_animations', False)
options.set_editor_property('import_mesh', True)
options.set_editor_property('import_materials', False)
options.set_editor_property('import_textures', False)
options.set_editor_property('skeleton', None)
options.set_editor_property('create_physics_asset', False)
options.set_editor_property(
'mesh_type_to_import',
unreal.FBXImportType.FBXIT_SKELETAL_MESH)
options.skeletal_mesh_import_data.set_editor_property(
'import_content_type',
unreal.FBXImportContentType.FBXICT_ALL)
options.skeletal_mesh_import_data.set_editor_property(
'normal_import_method',
unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS)
task.options = options
return task
def import_and_containerize(
self, filepath, asset_dir, asset_name, container_name
):
unreal.EditorAssetLibrary.make_directory(asset_dir)
task = self.get_task(
filepath, asset_dir, asset_name, False)
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
# Create Asset Container
create_container(container=container_name, path=asset_dir)
def imprint(
self, asset, asset_dir, container_name, asset_name, representation
):
data = {
"schema": "ayon:container-2.0",
"id": AYON_CONTAINER_ID,
"asset": asset,
"namespace": asset_dir,
"container_name": container_name,
"asset_name": asset_name,
"loader": str(self.__class__.__name__),
"representation": representation["_id"],
"parent": representation["parent"],
"family": representation["context"]["family"]
}
imprint(f"{asset_dir}/{container_name}", data)
def load(self, context, name, namespace, options):
"""Load and containerise representation into Content Browser.
This is a two step process. First, import FBX to temporary path and
then call `containerise()` on it - this moves all content to new
directory and then it will create AssetContainer there and imprint it
with metadata. This will mark this path as container.
Args:
context (dict): application context
name (str): subset name
@ -35,23 +104,15 @@ class SkeletalMeshFBXLoader(plugin.Loader):
This is not passed here, so namespace is set
by `containerise()` because only then we know
real path.
options (dict): Those would be data to be imprinted. This is not
used now, data are imprinted by `containerise()`.
data (dict): Those would be data to be imprinted.
Returns:
list(str): list of container content
"""
# Create directory for asset and Ayon container
root = "/Game/Ayon/Assets"
if options and options.get("asset_dir"):
root = options["asset_dir"]
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:
asset_name = "{}_{}".format(asset, name)
else:
asset_name = "{}".format(name)
asset_name = f"{asset}_{name}" if asset else f"{name}"
version = context.get('version')
# Check if version is hero version and use different name
if not version.get("name") and version.get('type') == "hero_version":
@ -61,67 +122,20 @@ class SkeletalMeshFBXLoader(plugin.Loader):
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
f"{root}/{asset}/{name_version}", suffix="")
f"{self.root}/{asset}/{name_version}", suffix=""
)
container_name += suffix
if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir):
unreal.EditorAssetLibrary.make_directory(asset_dir)
task = unreal.AssetImportTask()
path = self.filepath_from_context(context)
task.set_editor_property('filename', path)
task.set_editor_property('destination_path', asset_dir)
task.set_editor_property('destination_name', asset_name)
task.set_editor_property('replace_existing', False)
task.set_editor_property('automated', True)
task.set_editor_property('save', False)
# set import options here
options = unreal.FbxImportUI()
options.set_editor_property('import_as_skeletal', True)
options.set_editor_property('import_animations', False)
options.set_editor_property('import_mesh', True)
options.set_editor_property('import_materials', False)
options.set_editor_property('import_textures', False)
options.set_editor_property('skeleton', None)
options.set_editor_property('create_physics_asset', False)
self.import_and_containerize(
path, asset_dir, asset_name, container_name)
options.set_editor_property(
'mesh_type_to_import',
unreal.FBXImportType.FBXIT_SKELETAL_MESH)
options.skeletal_mesh_import_data.set_editor_property(
'import_content_type',
unreal.FBXImportContentType.FBXICT_ALL)
# set to import normals, otherwise Unreal will compute them
# and it will take a long time, depending on the size of the mesh
options.skeletal_mesh_import_data.set_editor_property(
'normal_import_method',
unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS)
task.options = options
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) # noqa: E501
# Create Asset Container
unreal_pipeline.create_container(
container=container_name, path=asset_dir)
data = {
"schema": "ayon:container-2.0",
"id": AYON_CONTAINER_ID,
"asset": asset,
"namespace": asset_dir,
"container_name": container_name,
"asset_name": asset_name,
"loader": str(self.__class__.__name__),
"representation": context["representation"]["_id"],
"parent": context["representation"]["parent"],
"family": context["representation"]["context"]["family"]
}
unreal_pipeline.imprint(
f"{asset_dir}/{container_name}", data)
self.imprint(
asset, asset_dir, container_name, asset_name,
context["representation"])
asset_content = unreal.EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=True
@ -133,58 +147,36 @@ class SkeletalMeshFBXLoader(plugin.Loader):
return asset_content
def update(self, container, representation):
name = container["asset_name"]
source_path = get_representation_path(representation)
destination_path = container["namespace"]
context = representation.get("context", {})
task = unreal.AssetImportTask()
if not context:
raise RuntimeError("No context found in representation")
task.set_editor_property('filename', source_path)
task.set_editor_property('destination_path', destination_path)
task.set_editor_property('destination_name', name)
task.set_editor_property('replace_existing', True)
task.set_editor_property('automated', True)
task.set_editor_property('save', True)
# Create directory for asset and Ayon container
asset = context.get('asset')
name = context.get('subset')
suffix = "_CON"
asset_name = f"{asset}_{name}" if asset else f"{name}"
version = context.get('version')
# Check if version is hero version and use different name
name_version = f"{name}_v{version:03d}" if version else f"{name}_hero"
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
f"{self.root}/{asset}/{name_version}", suffix="")
# set import options here
options = unreal.FbxImportUI()
options.set_editor_property('import_as_skeletal', True)
options.set_editor_property('import_animations', False)
options.set_editor_property('import_mesh', True)
options.set_editor_property('import_materials', True)
options.set_editor_property('import_textures', True)
options.set_editor_property('skeleton', None)
options.set_editor_property('create_physics_asset', False)
container_name += suffix
options.set_editor_property('mesh_type_to_import',
unreal.FBXImportType.FBXIT_SKELETAL_MESH)
if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir):
path = get_representation_path(representation)
options.skeletal_mesh_import_data.set_editor_property(
'import_content_type',
unreal.FBXImportContentType.FBXICT_ALL
)
# set to import normals, otherwise Unreal will compute them
# and it will take a long time, depending on the size of the mesh
options.skeletal_mesh_import_data.set_editor_property(
'normal_import_method',
unreal.FBXNormalImportMethod.FBXNIM_IMPORT_NORMALS
)
self.import_and_containerize(
path, asset_dir, asset_name, container_name)
task.options = options
# do import fbx and replace existing data
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) # noqa: E501
container_path = "{}/{}".format(container["namespace"],
container["objectName"])
# update metadata
unreal_pipeline.imprint(
container_path,
{
"representation": str(representation["_id"]),
"parent": str(representation["parent"])
})
self.imprint(
asset, asset_dir, container_name, asset_name, representation)
asset_content = unreal.EditorAssetLibrary.list_assets(
destination_path, recursive=True, include_folder=True
asset_dir, recursive=True, include_folder=False
)
for a in asset_content:

View file

@ -7,7 +7,11 @@ from openpype.pipeline import (
AYON_CONTAINER_ID
)
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
from openpype.hosts.unreal.api.pipeline import (
AYON_ASSET_DIR,
create_container,
imprint,
)
import unreal # noqa
@ -20,6 +24,8 @@ class StaticMeshAlembicLoader(plugin.Loader):
icon = "cube"
color = "orange"
root = AYON_ASSET_DIR
@staticmethod
def get_task(filename, asset_dir, asset_name, replace, default_conversion):
task = unreal.AssetImportTask()
@ -53,14 +59,40 @@ class StaticMeshAlembicLoader(plugin.Loader):
return task
def import_and_containerize(
self, filepath, asset_dir, asset_name, container_name,
default_conversion=False
):
unreal.EditorAssetLibrary.make_directory(asset_dir)
task = self.get_task(
filepath, asset_dir, asset_name, False, default_conversion)
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
# Create Asset Container
create_container(container=container_name, path=asset_dir)
def imprint(
self, asset, asset_dir, container_name, asset_name, representation
):
data = {
"schema": "ayon:container-2.0",
"id": AYON_CONTAINER_ID,
"asset": asset,
"namespace": asset_dir,
"container_name": container_name,
"asset_name": asset_name,
"loader": str(self.__class__.__name__),
"representation": representation["_id"],
"parent": representation["parent"],
"family": representation["context"]["family"]
}
imprint(f"{asset_dir}/{container_name}", data)
def load(self, context, name, namespace, options):
"""Load and containerise representation into Content Browser.
This is two step process. First, import FBX to temporary path and
then call `containerise()` on it - this moves all content to new
directory and then it will create AssetContainer there and imprint it
with metadata. This will mark this path as container.
Args:
context (dict): application context
name (str): subset name
@ -68,15 +100,12 @@ class StaticMeshAlembicLoader(plugin.Loader):
This is not passed here, so namespace is set
by `containerise()` because only then we know
real path.
data (dict): Those would be data to be imprinted. This is not used
now, data are imprinted by `containerise()`.
data (dict): Those would be data to be imprinted.
Returns:
list(str): list of container content
"""
# Create directory for asset and Ayon container
root = "/Game/Ayon/Assets"
asset = context.get('asset').get('name')
suffix = "_CON"
asset_name = f"{asset}_{name}" if asset else f"{name}"
@ -93,39 +122,22 @@ class StaticMeshAlembicLoader(plugin.Loader):
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
f"{root}/{asset}/{name_version}", suffix="")
f"{self.root}/{asset}/{name_version}", suffix="")
container_name += suffix
if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir):
unreal.EditorAssetLibrary.make_directory(asset_dir)
path = self.filepath_from_context(context)
task = self.get_task(
path, asset_dir, asset_name, False, default_conversion)
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) # noqa: E501
self.import_and_containerize(path, asset_dir, asset_name,
container_name, default_conversion)
# Create Asset Container
unreal_pipeline.create_container(
container=container_name, path=asset_dir)
data = {
"schema": "ayon:container-2.0",
"id": AYON_CONTAINER_ID,
"asset": asset,
"namespace": asset_dir,
"container_name": container_name,
"asset_name": asset_name,
"loader": str(self.__class__.__name__),
"representation": context["representation"]["_id"],
"parent": context["representation"]["parent"],
"family": context["representation"]["context"]["family"]
}
unreal_pipeline.imprint(f"{asset_dir}/{container_name}", data)
self.imprint(
asset, asset_dir, container_name, asset_name,
context["representation"])
asset_content = unreal.EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=True
asset_dir, recursive=True, include_folder=False
)
for a in asset_content:
@ -134,27 +146,36 @@ class StaticMeshAlembicLoader(plugin.Loader):
return asset_content
def update(self, container, representation):
name = container["asset_name"]
source_path = get_representation_path(representation)
destination_path = container["namespace"]
context = representation.get("context", {})
task = self.get_task(source_path, destination_path, name, True, False)
if not context:
raise RuntimeError("No context found in representation")
# do import fbx and replace existing data
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
# Create directory for asset and Ayon container
asset = context.get('asset')
name = context.get('subset')
suffix = "_CON"
asset_name = f"{asset}_{name}" if asset else f"{name}"
version = context.get('version')
# Check if version is hero version and use different name
name_version = f"{name}_v{version:03d}" if version else f"{name}_hero"
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
f"{self.root}/{asset}/{name_version}", suffix="")
container_path = "{}/{}".format(container["namespace"],
container["objectName"])
# update metadata
unreal_pipeline.imprint(
container_path,
{
"representation": str(representation["_id"]),
"parent": str(representation["parent"])
})
container_name += suffix
if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir):
path = get_representation_path(representation)
self.import_and_containerize(path, asset_dir, asset_name,
container_name)
self.imprint(
asset, asset_dir, container_name, asset_name, representation)
asset_content = unreal.EditorAssetLibrary.list_assets(
destination_path, recursive=True, include_folder=True
asset_dir, recursive=True, include_folder=False
)
for a in asset_content:

View file

@ -7,7 +7,11 @@ from openpype.pipeline import (
AYON_CONTAINER_ID
)
from openpype.hosts.unreal.api import plugin
from openpype.hosts.unreal.api import pipeline as unreal_pipeline
from openpype.hosts.unreal.api.pipeline import (
AYON_ASSET_DIR,
create_container,
imprint,
)
import unreal # noqa
@ -20,6 +24,8 @@ class StaticMeshFBXLoader(plugin.Loader):
icon = "cube"
color = "orange"
root = AYON_ASSET_DIR
@staticmethod
def get_task(filename, asset_dir, asset_name, replace):
task = unreal.AssetImportTask()
@ -46,14 +52,39 @@ class StaticMeshFBXLoader(plugin.Loader):
return task
def import_and_containerize(
self, filepath, asset_dir, asset_name, container_name
):
unreal.EditorAssetLibrary.make_directory(asset_dir)
task = self.get_task(
filepath, asset_dir, asset_name, False)
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
# Create Asset Container
create_container(container=container_name, path=asset_dir)
def imprint(
self, asset, asset_dir, container_name, asset_name, representation
):
data = {
"schema": "ayon:container-2.0",
"id": AYON_CONTAINER_ID,
"asset": asset,
"namespace": asset_dir,
"container_name": container_name,
"asset_name": asset_name,
"loader": str(self.__class__.__name__),
"representation": representation["_id"],
"parent": representation["parent"],
"family": representation["context"]["family"]
}
imprint(f"{asset_dir}/{container_name}", data)
def load(self, context, name, namespace, options):
"""Load and containerise representation into Content Browser.
This is two step process. First, import FBX to temporary path and
then call `containerise()` on it - this moves all content to new
directory and then it will create AssetContainer there and imprint it
with metadata. This will mark this path as container.
Args:
context (dict): application context
name (str): subset name
@ -61,23 +92,15 @@ class StaticMeshFBXLoader(plugin.Loader):
This is not passed here, so namespace is set
by `containerise()` because only then we know
real path.
options (dict): Those would be data to be imprinted. This is not
used now, data are imprinted by `containerise()`.
options (dict): Those would be data to be imprinted.
Returns:
list(str): list of container content
"""
# Create directory for asset and Ayon container
root = "/Game/Ayon/Assets"
if options and options.get("asset_dir"):
root = options["asset_dir"]
asset = context.get('asset').get('name')
suffix = "_CON"
if asset:
asset_name = "{}_{}".format(asset, name)
else:
asset_name = "{}".format(name)
asset_name = f"{asset}_{name}" if asset else f"{name}"
version = context.get('version')
# Check if version is hero version and use different name
if not version.get("name") and version.get('type') == "hero_version":
@ -87,35 +110,20 @@ class StaticMeshFBXLoader(plugin.Loader):
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
f"{root}/{asset}/{name_version}", suffix=""
f"{self.root}/{asset}/{name_version}", suffix=""
)
container_name += suffix
unreal.EditorAssetLibrary.make_directory(asset_dir)
if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir):
path = self.filepath_from_context(context)
path = self.filepath_from_context(context)
task = self.get_task(path, asset_dir, asset_name, False)
self.import_and_containerize(
path, asset_dir, asset_name, container_name)
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task]) # noqa: E501
# Create Asset Container
unreal_pipeline.create_container(
container=container_name, path=asset_dir)
data = {
"schema": "ayon:container-2.0",
"id": AYON_CONTAINER_ID,
"asset": asset,
"namespace": asset_dir,
"container_name": container_name,
"asset_name": asset_name,
"loader": str(self.__class__.__name__),
"representation": context["representation"]["_id"],
"parent": context["representation"]["parent"],
"family": context["representation"]["context"]["family"]
}
unreal_pipeline.imprint(f"{asset_dir}/{container_name}", data)
self.imprint(
asset, asset_dir, container_name, asset_name,
context["representation"])
asset_content = unreal.EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=True
@ -127,27 +135,36 @@ class StaticMeshFBXLoader(plugin.Loader):
return asset_content
def update(self, container, representation):
name = container["asset_name"]
source_path = get_representation_path(representation)
destination_path = container["namespace"]
context = representation.get("context", {})
task = self.get_task(source_path, destination_path, name, True)
if not context:
raise RuntimeError("No context found in representation")
# do import fbx and replace existing data
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])
# Create directory for asset and Ayon container
asset = context.get('asset')
name = context.get('subset')
suffix = "_CON"
asset_name = f"{asset}_{name}" if asset else f"{name}"
version = context.get('version')
# Check if version is hero version and use different name
name_version = f"{name}_v{version:03d}" if version else f"{name}_hero"
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
f"{self.root}/{asset}/{name_version}", suffix="")
container_path = "{}/{}".format(container["namespace"],
container["objectName"])
# update metadata
unreal_pipeline.imprint(
container_path,
{
"representation": str(representation["_id"]),
"parent": str(representation["parent"])
})
container_name += suffix
if not unreal.EditorAssetLibrary.does_directory_exist(asset_dir):
path = get_representation_path(representation)
self.import_and_containerize(
path, asset_dir, asset_name, container_name)
self.imprint(
asset, asset_dir, container_name, asset_name, representation)
asset_content = unreal.EditorAssetLibrary.list_assets(
destination_path, recursive=True, include_folder=True
asset_dir, recursive=True, include_folder=False
)
for a in asset_content:

View file

@ -41,7 +41,7 @@ class UAssetLoader(plugin.Loader):
"""
# Create directory for asset and Ayon container
root = "/Game/Ayon/Assets"
root = unreal_pipeline.AYON_ASSET_DIR
asset = context.get('asset').get('name')
suffix = "_CON"
asset_name = f"{asset}_{name}" if asset else f"{name}"

View file

@ -86,7 +86,7 @@ class YetiLoader(plugin.Loader):
raise RuntimeError("Groom plugin is not activated.")
# Create directory for asset and Ayon container
root = "/Game/Ayon/Assets"
root = unreal_pipeline.AYON_ASSET_DIR
asset = context.get('asset').get('name')
suffix = "_CON"
asset_name = f"{asset}_{name}" if asset else f"{name}"

View file

@ -3,6 +3,7 @@ import os
import re
import pyblish.api
from openpype.pipeline.publish import PublishValidationError
class ValidateSequenceFrames(pyblish.api.InstancePlugin):
@ -39,8 +40,22 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin):
collections, remainder = clique.assemble(
repr["files"], minimum_items=1, patterns=patterns)
assert not remainder, "Must not have remainder"
assert len(collections) == 1, "Must detect single collection"
if remainder:
raise PublishValidationError(
"Some files have been found outside a sequence. "
f"Invalid files: {remainder}")
if not collections:
raise PublishValidationError(
"We have been unable to find a sequence in the "
"files. Please ensure the files are named "
"appropriately. "
f"Files: {repr_files}")
if len(collections) > 1:
raise PublishValidationError(
"Multiple collections detected. There should be a single "
"collection per representation. "
f"Collections identified: {collections}")
collection = collections[0]
frames = list(collection.indexes)
@ -53,8 +68,12 @@ class ValidateSequenceFrames(pyblish.api.InstancePlugin):
data["clipOut"])
if current_range != required_range:
raise ValueError(f"Invalid frame range: {current_range} - "
f"expected: {required_range}")
raise PublishValidationError(
f"Invalid frame range: {current_range} - "
f"expected: {required_range}")
missing = collection.holes().indexes
assert not missing, "Missing frames: %s" % (missing,)
if missing:
raise PublishValidationError(
"Missing frames have been detected. "
f"Missing frames: {missing}")

View file

@ -25,6 +25,12 @@
},
"shelves": [],
"create": {
"CreateAlembicCamera": {
"enabled": true,
"default_variants": [
"Main"
]
},
"CreateArnoldAss": {
"enabled": true,
"default_variants": [
@ -32,6 +38,66 @@
],
"ext": ".ass"
},
"CreateArnoldRop": {
"enabled": true,
"default_variants": [
"Main"
]
},
"CreateCompositeSequence": {
"enabled": true,
"default_variants": [
"Main"
]
},
"CreateHDA": {
"enabled": true,
"default_variants": [
"Main"
]
},
"CreateKarmaROP": {
"enabled": true,
"default_variants": [
"Main"
]
},
"CreateMantraROP": {
"enabled": true,
"default_variants": [
"Main"
]
},
"CreatePointCache": {
"enabled": true,
"default_variants": [
"Main"
]
},
"CreateBGEO": {
"enabled": true,
"default_variants": [
"Main"
]
},
"CreateRedshiftProxy": {
"enabled": true,
"default_variants": [
"Main"
]
},
"CreateRedshiftROP": {
"enabled": true,
"default_variants": [
"Main"
]
},
"CreateReview": {
"enabled": true,
"default_variants": [
"Main"
]
},
"CreateStaticMesh": {
"enabled": true,
"default_variants": [
@ -45,31 +111,13 @@
"UCX"
]
},
"CreateAlembicCamera": {
"CreateUSD": {
"enabled": true,
"default_variants": [
"Main"
]
},
"CreateCompositeSequence": {
"enabled": true,
"default_variants": [
"Main"
]
},
"CreatePointCache": {
"enabled": true,
"default_variants": [
"Main"
]
},
"CreateRedshiftROP": {
"enabled": true,
"default_variants": [
"Main"
]
},
"CreateRemotePublish": {
"CreateUSDRender": {
"enabled": true,
"default_variants": [
"Main"
@ -81,26 +129,8 @@
"Main"
]
},
"CreateUSD": {
"enabled": false,
"default_variants": [
"Main"
]
},
"CreateUSDModel": {
"enabled": false,
"default_variants": [
"Main"
]
},
"USDCreateShadingWorkspace": {
"enabled": false,
"default_variants": [
"Main"
]
},
"CreateUSDRender": {
"enabled": false,
"CreateVrayROP": {
"enabled": true,
"default_variants": [
"Main"
]
@ -110,6 +140,31 @@
"CollectRopFrameRange": {
"use_asset_handles": true
},
"ValidateContainers": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateMeshIsStatic": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateReviewColorspace": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateSubsetName": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateUnrealStaticMeshName": {
"enabled": false,
"optional": true,
"active": true
},
"ValidateWorkfilePaths": {
"enabled": true,
"optional": true,
@ -121,31 +176,6 @@
"$HIP",
"$JOB"
]
},
"ValidateReviewColorspace": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateContainers": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateSubsetName": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateMeshIsStatic": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateUnrealStaticMeshName": {
"enabled": false,
"optional": true,
"active": true
}
}
}

View file

@ -4,6 +4,16 @@
"key": "create",
"label": "Creator plugins",
"children": [
{
"type": "schema_template",
"name": "template_create_plugin",
"template_data": [
{
"key": "CreateAlembicCamera",
"label": "Create Alembic Camera"
}
]
},
{
"type": "dict",
"collapsible": true,
@ -39,6 +49,52 @@
]
},
{
"type": "schema_template",
"name": "template_create_plugin",
"template_data": [
{
"key": "CreateArnoldRop",
"label": "Create Arnold ROP"
},
{
"key": "CreateCompositeSequence",
"label": "Create Composite (Image Sequence)"
},
{
"key": "CreateHDA",
"label": "Create Houdini Digital Asset"
},
{
"key": "CreateKarmaROP",
"label": "Create Karma ROP"
},
{
"key": "CreateMantraROP",
"label": "Create Mantra ROP"
},
{
"key": "CreatePointCache",
"label": "Create PointCache (Abc)"
},
{
"key": "CreateBGEO",
"label": "Create PointCache (Bgeo)"
},
{
"key": "CreateRedshiftProxy",
"label": "Create Redshift Proxy"
},
{
"key": "CreateRedshiftROP",
"label": "Create Redshift ROP"
},
{
"key": "CreateReview",
"label": "Create Review"
}
]
},
{
"type": "dict",
"collapsible": true,
@ -75,44 +131,20 @@
"name": "template_create_plugin",
"template_data": [
{
"key": "CreateAlembicCamera",
"label": "Create Alembic Camera"
"key": "CreateUSD",
"label": "Create USD (experimental)"
},
{
"key": "CreateCompositeSequence",
"label": "Create Composite (Image Sequence)"
},
{
"key": "CreatePointCache",
"label": "Create Point Cache"
},
{
"key": "CreateRedshiftROP",
"label": "Create Redshift ROP"
},
{
"key": "CreateRemotePublish",
"label": "Create Remote Publish"
"key": "CreateUSDRender",
"label": "Create USD render (experimental)"
},
{
"key": "CreateVDBCache",
"label": "Create VDB Cache"
},
{
"key": "CreateUSD",
"label": "Create USD"
},
{
"key": "CreateUSDModel",
"label": "Create USD Model"
},
{
"key": "USDCreateShadingWorkspace",
"label": "Create USD Shading Workspace"
},
{
"key": "CreateUSDRender",
"label": "Create USD Render"
"key": "CreateVrayROP",
"label": "Create VRay ROP"
}
]
}

View file

@ -25,6 +25,36 @@
}
]
},
{
"type": "label",
"label": "Validators"
},
{
"type": "schema_template",
"name": "template_publish_plugin",
"template_data": [
{
"key": "ValidateContainers",
"label": "Validate Containers"
},
{
"key": "ValidateMeshIsStatic",
"label": "Validate Mesh is Static"
},
{
"key": "ValidateReviewColorspace",
"label": "Validate Review Colorspace"
},
{
"key": "ValidateSubsetName",
"label": "Validate Subset Name"
},
{
"key": "ValidateUnrealStaticMeshName",
"label": "Validate Unreal Static Mesh Name"
}
]
},
{
"type": "dict",
"collapsible": true,
@ -56,32 +86,6 @@
"object_type": "text"
}
]
},
{
"type": "schema_template",
"name": "template_publish_plugin",
"template_data": [
{
"key": "ValidateReviewColorspace",
"label": "Validate Review Colorspace"
},
{
"key": "ValidateContainers",
"label": "ValidateContainers"
},
{
"key": "ValidateSubsetName",
"label": "Validate Subset Name"
},
{
"key": "ValidateMeshIsStatic",
"label": "Validate Mesh is Static"
},
{
"key": "ValidateUnrealStaticMeshName",
"label": "Validate Unreal Static Mesh Name"
}
]
}
]
}

View file

@ -40,6 +40,10 @@
"object_type": {
"type": "dict",
"children": [
{
"type": "label",
"label": "Name and Script Path are mandatory."
},
{
"type": "text",
"key": "label",
@ -68,4 +72,4 @@
}
]
}
}
}

View file

@ -298,6 +298,7 @@ class NumberAttrWidget(_BaseAttrDefWidget):
input_widget.installEventFilter(self)
multisel_widget = ClickableLineEdit("< Multiselection >", self)
multisel_widget.setVisible(False)
input_widget.valueChanged.connect(self._on_value_change)
multisel_widget.clicked.connect(self._on_multi_click)

View file

@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring Pype version."""
__version__ = "3.17.4-nightly.2"
__version__ = "3.17.4"

View file

@ -1,6 +1,6 @@
[tool.poetry]
name = "OpenPype"
version = "3.17.3" # OpenPype
version = "3.17.4" # OpenPype
description = "Open VFX and Animation pipeline with support."
authors = ["OpenPype Team <info@openpype.io>"]
license = "MIT License"

View file

@ -1,5 +1,4 @@
from pydantic import Field
from ayon_server.settings import BaseSettingsModel
@ -35,52 +34,110 @@ class CreateStaticMeshModel(BaseSettingsModel):
class CreatePluginsModel(BaseSettingsModel):
CreateArnoldAss: CreateArnoldAssModel = Field(
default_factory=CreateArnoldAssModel,
title="Create Alembic Camera")
# "-" is not compatible in the new model
CreateStaticMesh: CreateStaticMeshModel = Field(
default_factory=CreateStaticMeshModel,
title="Create Static Mesh"
)
CreateAlembicCamera: CreatorModel = Field(
default_factory=CreatorModel,
title="Create Alembic Camera")
CreateArnoldAss: CreateArnoldAssModel = Field(
default_factory=CreateArnoldAssModel,
title="Create Arnold Ass")
CreateArnoldRop: CreatorModel = Field(
default_factory=CreatorModel,
title="Create Arnold ROP")
CreateCompositeSequence: CreatorModel = Field(
default_factory=CreatorModel,
title="Create Composite Sequence")
title="Create Composite (Image Sequence)")
CreateHDA: CreatorModel = Field(
default_factory=CreatorModel,
title="Create Houdini Digital Asset")
CreateKarmaROP: CreatorModel = Field(
default_factory=CreatorModel,
title="Create Karma ROP")
CreateMantraROP: CreatorModel = Field(
default_factory=CreatorModel,
title="Create Mantra ROP")
CreatePointCache: CreatorModel = Field(
default_factory=CreatorModel,
title="Create Point Cache")
title="Create PointCache (Abc)")
CreateBGEO: CreatorModel = Field(
default_factory=CreatorModel,
title="Create PointCache (Bgeo)")
CreateRedshiftProxy: CreatorModel = Field(
default_factory=CreatorModel,
title="Create Redshift Proxy")
CreateRedshiftROP: CreatorModel = Field(
default_factory=CreatorModel,
title="Create RedshiftROP")
CreateRemotePublish: CreatorModel = Field(
title="Create Redshift ROP")
CreateReview: CreatorModel = Field(
default_factory=CreatorModel,
title="Create Remote Publish")
title="Create Review")
# "-" is not compatible in the new model
CreateStaticMesh: CreateStaticMeshModel = Field(
default_factory=CreateStaticMeshModel,
title="Create Static Mesh")
CreateUSD: CreatorModel = Field(
default_factory=CreatorModel,
title="Create USD (experimental)")
CreateUSDRender: CreatorModel = Field(
default_factory=CreatorModel,
title="Create USD render (experimental)")
CreateVDBCache: CreatorModel = Field(
default_factory=CreatorModel,
title="Create VDB Cache")
CreateUSD: CreatorModel = Field(
CreateVrayROP: CreatorModel = Field(
default_factory=CreatorModel,
title="Create USD")
CreateUSDModel: CreatorModel = Field(
default_factory=CreatorModel,
title="Create USD model")
USDCreateShadingWorkspace: CreatorModel = Field(
default_factory=CreatorModel,
title="Create USD shading workspace")
CreateUSDRender: CreatorModel = Field(
default_factory=CreatorModel,
title="Create USD render")
title="Create VRay ROP")
DEFAULT_HOUDINI_CREATE_SETTINGS = {
"CreateAlembicCamera": {
"enabled": True,
"default_variants": ["Main"]
},
"CreateArnoldAss": {
"enabled": True,
"default_variants": ["Main"],
"ext": ".ass"
},
"CreateArnoldRop": {
"enabled": True,
"default_variants": ["Main"]
},
"CreateCompositeSequence": {
"enabled": True,
"default_variants": ["Main"]
},
"CreateHDA": {
"enabled": True,
"default_variants": ["Main"]
},
"CreateKarmaROP": {
"enabled": True,
"default_variants": ["Main"]
},
"CreateMantraROP": {
"enabled": True,
"default_variants": ["Main"]
},
"CreatePointCache": {
"enabled": True,
"default_variants": ["Main"]
},
"CreateBGEO": {
"enabled": True,
"default_variants": ["Main"]
},
"CreateRedshiftProxy": {
"enabled": True,
"default_variants": ["Main"]
},
"CreateRedshiftROP": {
"enabled": True,
"default_variants": ["Main"]
},
"CreateReview": {
"enabled": True,
"default_variants": ["Main"]
},
"CreateStaticMesh": {
"enabled": True,
"default_variants": [
@ -94,145 +151,20 @@ DEFAULT_HOUDINI_CREATE_SETTINGS = {
"UCX"
]
},
"CreateAlembicCamera": {
"enabled": True,
"default_variants": ["Main"]
},
"CreateCompositeSequence": {
"enabled": True,
"default_variants": ["Main"]
},
"CreatePointCache": {
"enabled": True,
"default_variants": ["Main"]
},
"CreateRedshiftROP": {
"enabled": True,
"default_variants": ["Main"]
},
"CreateRemotePublish": {
"enabled": True,
"default_variants": ["Main"]
},
"CreateVDBCache": {
"enabled": True,
"default_variants": ["Main"]
},
"CreateUSD": {
"enabled": False,
"default_variants": ["Main"]
},
"CreateUSDModel": {
"enabled": False,
"default_variants": ["Main"]
},
"USDCreateShadingWorkspace": {
"enabled": False,
"default_variants": ["Main"]
},
"CreateUSDRender": {
"enabled": False,
"default_variants": ["Main"]
},
}
# Publish Plugins
class ValidateWorkfilePathsModel(BaseSettingsModel):
enabled: bool = Field(title="Enabled")
optional: bool = Field(title="Optional")
node_types: list[str] = Field(
default_factory=list,
title="Node Types"
)
prohibited_vars: list[str] = Field(
default_factory=list,
title="Prohibited Variables"
)
class CollectRopFrameRangeModel(BaseSettingsModel):
"""Collect Frame Range
Disable this if you want the publisher to
ignore start and end handles specified in the
asset data for publish instances
"""
use_asset_handles: bool = Field(
title="Use asset handles")
class BasicValidateModel(BaseSettingsModel):
enabled: bool = Field(title="Enabled")
optional: bool = Field(title="Optional")
active: bool = Field(title="Active")
class PublishPluginsModel(BaseSettingsModel):
CollectRopFrameRange: CollectRopFrameRangeModel = Field(
default_factory=CollectRopFrameRangeModel,
title="Collect Rop Frame Range.",
section="Collectors"
)
ValidateWorkfilePaths: ValidateWorkfilePathsModel = Field(
default_factory=ValidateWorkfilePathsModel,
title="Validate workfile paths settings.")
ValidateReviewColorspace: BasicValidateModel = Field(
default_factory=BasicValidateModel,
title="Validate Review Colorspace.")
ValidateContainers: BasicValidateModel = Field(
default_factory=BasicValidateModel,
title="Validate Latest Containers.")
ValidateSubsetName: BasicValidateModel = Field(
default_factory=BasicValidateModel,
title="Validate Subset Name.")
ValidateMeshIsStatic: BasicValidateModel = Field(
default_factory=BasicValidateModel,
title="Validate Mesh is Static.")
ValidateUnrealStaticMeshName: BasicValidateModel = Field(
default_factory=BasicValidateModel,
title="Validate Unreal Static Mesh Name.")
DEFAULT_HOUDINI_PUBLISH_SETTINGS = {
"CollectRopFrameRange": {
"use_asset_handles": True
},
"ValidateWorkfilePaths": {
"enabled": True,
"optional": True,
"node_types": [
"file",
"alembic"
],
"prohibited_vars": [
"$HIP",
"$JOB"
]
},
"ValidateReviewColorspace": {
"enabled": True,
"optional": True,
"active": True
},
"ValidateContainers": {
"enabled": True,
"optional": True,
"active": True
},
"ValidateSubsetName": {
"enabled": True,
"optional": True,
"active": True
},
"ValidateMeshIsStatic": {
"enabled": True,
"optional": True,
"active": True
},
"ValidateUnrealStaticMeshName": {
"enabled": False,
"optional": True,
"active": True
}
"CreateVDBCache": {
"enabled": True,
"default_variants": ["Main"]
},
"CreateVrayROP": {
"enabled": True,
"default_variants": ["Main"]
},
}

View file

@ -1,57 +1,19 @@
from pydantic import Field
from ayon_server.settings import (
BaseSettingsModel,
MultiplatformPathModel,
MultiplatformPathListModel,
)
from ayon_server.settings import BaseSettingsModel
from .general import (
GeneralSettingsModel,
DEFAULT_GENERAL_SETTINGS
)
from .imageio import HoudiniImageIOModel
from .publish_plugins import (
PublishPluginsModel,
from .shelves import ShelvesModel
from .create import (
CreatePluginsModel,
DEFAULT_HOUDINI_PUBLISH_SETTINGS,
DEFAULT_HOUDINI_CREATE_SETTINGS
)
class ShelfToolsModel(BaseSettingsModel):
name: str = Field(title="Name")
help: str = Field(title="Help text")
script: MultiplatformPathModel = Field(
default_factory=MultiplatformPathModel,
title="Script Path "
)
icon: MultiplatformPathModel = Field(
default_factory=MultiplatformPathModel,
title="Icon Path "
)
class ShelfDefinitionModel(BaseSettingsModel):
_layout = "expanded"
shelf_name: str = Field(title="Shelf name")
tools_list: list[ShelfToolsModel] = Field(
default_factory=list,
title="Shelf Tools"
)
class ShelvesModel(BaseSettingsModel):
_layout = "expanded"
shelf_set_name: str = Field(title="Shelfs set name")
shelf_set_source_path: MultiplatformPathListModel = Field(
default_factory=MultiplatformPathListModel,
title="Shelf Set Path (optional)"
)
shelf_definition: list[ShelfDefinitionModel] = Field(
default_factory=list,
title="Shelf Definitions"
)
from .publish import (
PublishPluginsModel,
DEFAULT_HOUDINI_PUBLISH_SETTINGS,
)
class HoudiniSettings(BaseSettingsModel):
@ -65,18 +27,16 @@ class HoudiniSettings(BaseSettingsModel):
)
shelves: list[ShelvesModel] = Field(
default_factory=list,
title="Houdini Scripts Shelves",
title="Shelves Manager",
)
publish: PublishPluginsModel = Field(
default_factory=PublishPluginsModel,
title="Publish Plugins",
)
create: CreatePluginsModel = Field(
default_factory=CreatePluginsModel,
title="Creator Plugins",
)
publish: PublishPluginsModel = Field(
default_factory=PublishPluginsModel,
title="Publish Plugins",
)
DEFAULT_VALUES = {

View file

@ -0,0 +1,103 @@
from pydantic import Field
from ayon_server.settings import BaseSettingsModel
# Publish Plugins
class CollectRopFrameRangeModel(BaseSettingsModel):
"""Collect Frame Range
Disable this if you want the publisher to
ignore start and end handles specified in the
asset data for publish instances
"""
use_asset_handles: bool = Field(
title="Use asset handles")
class ValidateWorkfilePathsModel(BaseSettingsModel):
enabled: bool = Field(title="Enabled")
optional: bool = Field(title="Optional")
node_types: list[str] = Field(
default_factory=list,
title="Node Types"
)
prohibited_vars: list[str] = Field(
default_factory=list,
title="Prohibited Variables"
)
class BasicValidateModel(BaseSettingsModel):
enabled: bool = Field(title="Enabled")
optional: bool = Field(title="Optional")
active: bool = Field(title="Active")
class PublishPluginsModel(BaseSettingsModel):
CollectRopFrameRange: CollectRopFrameRangeModel = Field(
default_factory=CollectRopFrameRangeModel,
title="Collect Rop Frame Range.",
section="Collectors"
)
ValidateContainers: BasicValidateModel = Field(
default_factory=BasicValidateModel,
title="Validate Latest Containers.",
section="Validators")
ValidateMeshIsStatic: BasicValidateModel = Field(
default_factory=BasicValidateModel,
title="Validate Mesh is Static.")
ValidateReviewColorspace: BasicValidateModel = Field(
default_factory=BasicValidateModel,
title="Validate Review Colorspace.")
ValidateSubsetName: BasicValidateModel = Field(
default_factory=BasicValidateModel,
title="Validate Subset Name.")
ValidateUnrealStaticMeshName: BasicValidateModel = Field(
default_factory=BasicValidateModel,
title="Validate Unreal Static Mesh Name.")
ValidateWorkfilePaths: ValidateWorkfilePathsModel = Field(
default_factory=ValidateWorkfilePathsModel,
title="Validate workfile paths settings.")
DEFAULT_HOUDINI_PUBLISH_SETTINGS = {
"CollectRopFrameRange": {
"use_asset_handles": True
},
"ValidateContainers": {
"enabled": True,
"optional": True,
"active": True
},
"ValidateMeshIsStatic": {
"enabled": True,
"optional": True,
"active": True
},
"ValidateReviewColorspace": {
"enabled": True,
"optional": True,
"active": True
},
"ValidateSubsetName": {
"enabled": True,
"optional": True,
"active": True
},
"ValidateUnrealStaticMeshName": {
"enabled": False,
"optional": True,
"active": True
},
"ValidateWorkfilePaths": {
"enabled": True,
"optional": True,
"node_types": [
"file",
"alembic"
],
"prohibited_vars": [
"$HIP",
"$JOB"
]
}
}

View file

@ -0,0 +1,37 @@
from pydantic import Field
from ayon_server.settings import (
BaseSettingsModel,
MultiplatformPathModel
)
class ShelfToolsModel(BaseSettingsModel):
"""Name and Script Path are mandatory."""
label: str = Field(title="Name")
script: str = Field(title="Script Path")
icon: str = Field("", title="Icon Path")
help: str = Field("", title="Help text")
class ShelfDefinitionModel(BaseSettingsModel):
_layout = "expanded"
shelf_name: str = Field(title="Shelf name")
tools_list: list[ShelfToolsModel] = Field(
default_factory=list,
title="Shelf Tools"
)
class ShelvesModel(BaseSettingsModel):
_layout = "expanded"
shelf_set_name: str = Field("", title="Shelfs set name")
shelf_set_source_path: MultiplatformPathModel = Field(
default_factory=MultiplatformPathModel,
title="Shelf Set Path (optional)"
)
shelf_definition: list[ShelfDefinitionModel] = Field(
default_factory=list,
title="Shelf Definitions"
)

View file

@ -1 +1 @@
__version__ = "0.1.6"
__version__ = "0.2.6"