.*)_(.*)_SHD"
},
"ValidateShadingEngine": {
@@ -222,6 +227,7 @@
},
"ValidateLoadedPlugin": {
"enabled": false,
+ "optional": true,
"whitelist_native_plugins": false,
"authorized_plugins": []
},
@@ -236,6 +242,7 @@
},
"ValidateUnrealStaticMeshName": {
"enabled": true,
+ "optional": true,
"validate_mesh": false,
"validate_collision": true
},
@@ -252,6 +259,81 @@
"redshift_render_attributes": [],
"renderman_render_attributes": []
},
+ "ValidateCurrentRenderLayerIsRenderable": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateRenderImageRule": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateRenderNoDefaultCameras": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateRenderSingleCamera": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateRenderLayerAOVs": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateStepSize": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateVRayDistributedRendering": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateVrayReferencedAOVs": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateVRayTranslatorEnabled": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateVrayProxy": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateVrayProxyMembers": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateYetiRenderScriptCallbacks": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateYetiRigCacheState": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateYetiRigInputShapesInInstance": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateYetiRigSettings": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
"ValidateModelName": {
"enabled": false,
"database": true,
@@ -270,6 +352,7 @@
},
"ValidateTransformNamingSuffix": {
"enabled": true,
+ "optional": true,
"SUFFIX_NAMING_TABLE": {
"mesh": [
"_GEO",
@@ -293,7 +376,7 @@
"ALLOW_IF_NOT_IN_SUFFIX_TABLE": true
},
"ValidateColorSets": {
- "enabled": false,
+ "enabled": true,
"optional": true,
"active": true
},
@@ -337,6 +420,16 @@
"optional": true,
"active": true
},
+ "ValidateMeshNoNegativeScale": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateMeshNonZeroEdgeLength": {
+ "enabled": true,
+ "optional": true,
+ "active": true
+ },
"ValidateMeshNormalsUnlocked": {
"enabled": false,
"optional": true,
@@ -359,22 +452,22 @@
},
"ValidateNoNamespace": {
"enabled": true,
- "optional": true,
+ "optional": false,
"active": true
},
"ValidateNoNullTransforms": {
"enabled": true,
- "optional": true,
+ "optional": false,
"active": true
},
"ValidateNoUnknownNodes": {
"enabled": true,
- "optional": true,
+ "optional": false,
"active": true
},
"ValidateNodeNoGhosting": {
"enabled": false,
- "optional": true,
+ "optional": false,
"active": true
},
"ValidateShapeDefaultNames": {
@@ -402,6 +495,21 @@
"optional": true,
"active": true
},
+ "ValidateNoVRayMesh": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateUnrealMeshTriangulated": {
+ "enabled": false,
+ "optional": true,
+ "active": true
+ },
+ "ValidateAlembicVisibleOnly": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
"ExtractAlembic": {
"enabled": true,
"families": [
@@ -425,8 +533,34 @@
"optional": true,
"active": true
},
+ "ValidateAnimationContent": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateOutRelatedNodeIds": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateRigControllersArnoldAttributes": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateSkeletalMeshHierarchy": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateSkinclusterDeformerSet": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
"ValidateRigOutSetNodeIds": {
"enabled": true,
+ "optional": false,
"allow_history_only": false
},
"ValidateCameraAttributes": {
@@ -439,14 +573,44 @@
"optional": true,
"active": true
},
+ "ValidateAssemblyNamespaces": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateAssemblyModelTransforms": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
"ValidateAssRelativePaths": {
"enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateInstancerContent": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateInstancerFrameRanges": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateNoDefaultCameras": {
+ "enabled": true,
+ "optional": false,
+ "active": true
+ },
+ "ValidateUnrealUpAxis": {
+ "enabled": false,
"optional": true,
"active": true
},
"ValidateCameraContents": {
"enabled": true,
- "optional": true,
+ "optional": false,
"validate_shapes": true
},
"ExtractPlayblast": {
@@ -497,11 +661,29 @@
"override_viewport_options": true,
"displayLights": "default",
"textureMaxResolution": 1024,
- "multiSample": 4,
+ "renderDepthOfField": true,
"shadows": true,
"textures": true,
"twoSidedLighting": true,
- "ssaoEnable": true,
+ "lineAAEnable": true,
+ "multiSample": 8,
+ "ssaoEnable": false,
+ "ssaoAmount": 1,
+ "ssaoRadius": 16,
+ "ssaoFilterRadius": 16,
+ "ssaoSamples": 16,
+ "fogging": false,
+ "hwFogFalloff": "0",
+ "hwFogDensity": 0.0,
+ "hwFogStart": 0,
+ "hwFogEnd": 100,
+ "hwFogAlpha": 0,
+ "hwFogColorR": 1.0,
+ "hwFogColorG": 1.0,
+ "hwFogColorB": 1.0,
+ "motionBlurEnable": false,
+ "motionBlurSampleCount": 8,
+ "motionBlurShutterOpenFraction": 0.2,
"cameras": false,
"clipGhosts": false,
"controlVertices": false,
diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json
index 6c45e2a9c1..3e29122074 100644
--- a/openpype/settings/defaults/project_settings/nuke.json
+++ b/openpype/settings/defaults/project_settings/nuke.json
@@ -287,7 +287,11 @@
"LoadClip": {
"enabled": true,
"_representations": [],
- "node_name_template": "{class_name}_{ext}"
+ "node_name_template": "{class_name}_{ext}",
+ "options_defaults": {
+ "start_at_workfile": true,
+ "add_retime": true
+ }
}
},
"workfile_builder": {
diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json
index 0b54cfd39e..8bf3e3b306 100644
--- a/openpype/settings/defaults/project_settings/traypublisher.json
+++ b/openpype/settings/defaults/project_settings/traypublisher.json
@@ -8,9 +8,10 @@
"default_variants": [
"Main"
],
- "description": "Publish workfile backup",
- "detailed_description": "",
- "allow_sequences": true,
+ "description": "Backup of a working scene",
+ "detailed_description": "Workfiles are full scenes from any application that are directly edited by artists. They represent a state of work on a task at a given point and are usually not directly referenced into other scenes.",
+ "allow_sequences": false,
+ "allow_multiple_items": false,
"extensions": [
".ma",
".mb",
@@ -30,6 +31,216 @@
".psb",
".aep"
]
+ },
+ {
+ "family": "model",
+ "identifier": "",
+ "label": "Model",
+ "icon": "fa.cubes",
+ "default_variants": [
+ "Main",
+ "Proxy",
+ "Sculpt"
+ ],
+ "description": "Clean models",
+ "detailed_description": "Models should only contain geometry data, without any extras like cameras, locators or bones.\n\nKeep in mind that models published from tray publisher are not validated for correctness. ",
+ "allow_sequences": false,
+ "allow_multiple_items": true,
+ "extensions": [
+ ".ma",
+ ".mb",
+ ".obj",
+ ".abc",
+ ".fbx",
+ ".bgeo",
+ ".bgeogz",
+ ".bgeosc",
+ ".usd",
+ ".blend"
+ ]
+ },
+ {
+ "family": "pointcache",
+ "identifier": "",
+ "label": "Pointcache",
+ "icon": "fa.gears",
+ "default_variants": [
+ "Main"
+ ],
+ "description": "Geometry Caches",
+ "detailed_description": "Alembic or bgeo cache of animated data",
+ "allow_sequences": true,
+ "allow_multiple_items": true,
+ "extensions": [
+ ".abc",
+ ".bgeo",
+ ".bgeogz",
+ ".bgeosc"
+ ]
+ },
+ {
+ "family": "plate",
+ "identifier": "",
+ "label": "Plate",
+ "icon": "mdi.camera-image",
+ "default_variants": [
+ "Main",
+ "BG",
+ "Animatic",
+ "Reference",
+ "Offline"
+ ],
+ "description": "Footage Plates",
+ "detailed_description": "Any type of image seqeuence coming from outside of the studio. Usually camera footage, but could also be animatics used for reference.",
+ "allow_sequences": true,
+ "allow_multiple_items": true,
+ "extensions": [
+ ".exr",
+ ".png",
+ ".dpx",
+ ".jpg",
+ ".tiff",
+ ".tif",
+ ".mov",
+ ".mp4",
+ ".avi"
+ ]
+ },
+ {
+ "family": "render",
+ "identifier": "",
+ "label": "Render",
+ "icon": "mdi.folder-multiple-image",
+ "default_variants": [],
+ "description": "Rendered images or video",
+ "detailed_description": "Sequence or single file renders",
+ "allow_sequences": true,
+ "allow_multiple_items": true,
+ "extensions": [
+ ".exr",
+ ".png",
+ ".dpx",
+ ".jpg",
+ ".jpeg",
+ ".tiff",
+ ".tif",
+ ".mov",
+ ".mp4",
+ ".avi"
+ ]
+ },
+ {
+ "family": "camera",
+ "identifier": "",
+ "label": "Camera",
+ "icon": "fa.video-camera",
+ "default_variants": [],
+ "description": "3d Camera",
+ "detailed_description": "Ideally this should be only camera itself with baked animation, however, it can technically also include helper geometry.",
+ "allow_sequences": false,
+ "allow_multiple_items": true,
+ "extensions": [
+ ".abc",
+ ".ma",
+ ".hip",
+ ".blend",
+ ".fbx",
+ ".usd"
+ ]
+ },
+ {
+ "family": "image",
+ "identifier": "",
+ "label": "Image",
+ "icon": "fa.image",
+ "default_variants": [
+ "Reference",
+ "Texture",
+ "Concept",
+ "Background"
+ ],
+ "description": "Single image",
+ "detailed_description": "Any image data can be published as image family. References, textures, concept art, matte paints. This is a fallback 2d family for everything that doesn't fit more specific family.",
+ "allow_sequences": false,
+ "allow_multiple_items": true,
+ "extensions": [
+ ".exr",
+ ".jpg",
+ ".jpeg",
+ ".dpx",
+ ".bmp",
+ ".tif",
+ ".tiff",
+ ".png",
+ ".psb",
+ ".psd"
+ ]
+ },
+ {
+ "family": "vdb",
+ "identifier": "",
+ "label": "VDB Volumes",
+ "icon": "fa.cloud",
+ "default_variants": [],
+ "description": "Sparse volumetric data",
+ "detailed_description": "Hierarchical data structure for the efficient storage and manipulation of sparse volumetric data discretized on three-dimensional grids",
+ "allow_sequences": true,
+ "allow_multiple_items": true,
+ "extensions": [
+ ".vdb"
+ ]
+ },
+ {
+ "family": "matchmove",
+ "identifier": "",
+ "label": "Matchmove",
+ "icon": "fa.empire",
+ "default_variants": [
+ "Camera",
+ "Object",
+ "Mocap"
+ ],
+ "description": "Matchmoving script",
+ "detailed_description": "Script exported from matchmoving application to be later processed into a tracked camera with additional data",
+ "allow_sequences": false,
+ "allow_multiple_items": true,
+ "extensions": []
+ },
+ {
+ "family": "rig",
+ "identifier": "",
+ "label": "Rig",
+ "icon": "fa.wheelchair",
+ "default_variants": [],
+ "description": "CG rig file",
+ "detailed_description": "CG rigged character or prop. Rig should be clean of any extra data and directly loadable into it's respective application\t",
+ "allow_sequences": false,
+ "allow_multiple_items": false,
+ "extensions": [
+ ".ma",
+ ".blend",
+ ".hip",
+ ".hda"
+ ]
+ },
+ {
+ "family": "simpleUnrealTexture",
+ "identifier": "",
+ "label": "Simple UE texture",
+ "icon": "fa.image",
+ "default_variants": [],
+ "description": "Simple Unreal Engine texture",
+ "detailed_description": "Texture files with Unreal Engine naming conventions",
+ "allow_sequences": false,
+ "allow_multiple_items": true,
+ "extensions": []
}
- ]
+ ],
+ "BatchMovieCreator": {
+ "default_variants": ["Main"],
+ "default_tasks": ["Compositing"],
+ "extensions": [
+ ".mov"
+ ]
+ }
}
\ No newline at end of file
diff --git a/openpype/settings/defaults/system_settings/general.json b/openpype/settings/defaults/system_settings/general.json
index a06947ba77..909ffc1ee4 100644
--- a/openpype/settings/defaults/system_settings/general.json
+++ b/openpype/settings/defaults/system_settings/general.json
@@ -2,11 +2,7 @@
"studio_name": "Studio name",
"studio_code": "stu",
"admin_password": "",
- "environment": {
- "__environment_keys__": {
- "global": []
- }
- },
+ "environment": {},
"log_to_server": true,
"disk_mapping": {
"windows": [],
diff --git a/openpype/settings/entities/enum_entity.py b/openpype/settings/entities/enum_entity.py
index 92a397afba..03998677ce 100644
--- a/openpype/settings/entities/enum_entity.py
+++ b/openpype/settings/entities/enum_entity.py
@@ -169,6 +169,7 @@ class HostsEnumEntity(BaseEnumEntity):
"tvpaint",
"unreal",
"standalonepublisher",
+ "traypublisher",
"webpublisher"
]
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json
index f8f9d5093d..e008fd85ee 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json
@@ -410,7 +410,41 @@
{
"type": "boolean",
"key": "cycle_enabled",
- "label": "Create daily review session"
+ "label": "Run automatically every day"
+ },
+ {
+ "type": "separator"
+ },
+ {
+ "type": "list-strict",
+ "key": "cycle_hour_start",
+ "label": "Create daily review session at",
+ "tooltip": "This may take affect on next day",
+ "object_types": [
+ {
+ "label": "H:",
+ "type": "number",
+ "minimum": 0,
+ "maximum": 23,
+ "decimal": 0
+ }, {
+ "label": "M:",
+ "type": "number",
+ "minimum": 0,
+ "maximum": 59,
+ "decimal": 0
+ }, {
+ "label": "S:",
+ "type": "number",
+ "minimum": 0,
+ "maximum": 59,
+ "decimal": 0
+ }
+ ]
+ },
+ {
+ "type": "label",
+ "label": "This can't be overriden per project and any change will take effect on the next day or on restart of event server."
},
{
"type": "separator"
@@ -822,7 +856,7 @@
},
{
"type": "label",
- "label": "Template may contain formatting keys intent, comment, host_name, app_name, app_label and published_paths."
+ "label": "Template may contain formatting keys intent, comment, host_name, app_name, app_label, published_paths and source."
},
{
"type": "text",
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json
index f717eff7dd..3108d2197e 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_hiero.json
@@ -206,6 +206,10 @@
{
"type": "schema",
"name": "schema_publish_gui_filter"
+ },
+ {
+ "type": "schema",
+ "name": "schema_scriptsmenu"
}
]
}
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json
index 55c1b7b7d7..8f0f864dc2 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json
@@ -67,6 +67,11 @@
"label": "Allow sequences",
"type": "boolean"
},
+ {
+ "key": "allow_multiple_items",
+ "label": "Allow multiple items",
+ "type": "boolean"
+ },
{
"type": "list",
"key": "extensions",
@@ -78,6 +83,44 @@
}
]
}
+ },
+ {
+ "type": "dict",
+ "collapsible": true,
+ "key": "BatchMovieCreator",
+ "label": "Batch Movie Creator",
+ "collapsible_key": true,
+ "children": [
+ {
+ "type": "label",
+ "label": "Allows to publish multiple video files in one go.
Name of matching asset is parsed from file names ('asset.mov', 'asset_v001.mov', 'my_asset_to_publish.mov')"
+ },
+ {
+ "type": "list",
+ "key": "default_variants",
+ "label": "Default variants",
+ "object_type": {
+ "type": "text"
+ }
+ },
+ {
+ "type": "list",
+ "key": "default_tasks",
+ "label": "Default tasks",
+ "object_type": {
+ "type": "text"
+ }
+ },
+ {
+ "type": "list",
+ "key": "extensions",
+ "label": "Extensions",
+ "use_label_wrap": true,
+ "collapsible_key": true,
+ "collapsed": false,
+ "object_type": "text"
+ }
+ ]
}
]
}
diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json
index d6b81c8687..7a40f349cc 100644
--- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json
+++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json
@@ -202,12 +202,15 @@
"decimal": 0
},
{
- "type": "number",
- "key": "multiSample",
- "label": "Anti Aliasing Samples",
- "decimal": 0,
- "minimum": 0,
- "maximum": 32
+ "type": "splitter"
+ },
+ {
+ "type":"boolean",
+ "key": "renderDepthOfField",
+ "label": "Depth of Field"
+ },
+ {
+ "type": "splitter"
},
{
"type": "boolean",
@@ -224,11 +227,145 @@
"key": "twoSidedLighting",
"label": "Two Sided Lighting"
},
+ {
+ "type": "splitter"
+ },
+ {
+ "type": "boolean",
+ "key": "lineAAEnable",
+ "label": "Enable Anti-Aliasing"
+ },
+ {
+ "type": "number",
+ "key": "multiSample",
+ "label": "Anti Aliasing Samples",
+ "decimal": 0,
+ "minimum": 0,
+ "maximum": 32
+ },
+ {
+ "type": "splitter"
+ },
{
"type": "boolean",
"key": "ssaoEnable",
"label": "Screen Space Ambient Occlusion"
},
+ {
+ "type": "number",
+ "key": "ssaoAmount",
+ "label": "SSAO Amount"
+ },
+ {
+ "type": "number",
+ "key": "ssaoRadius",
+ "label": "SSAO Radius"
+ },
+ {
+ "type": "number",
+ "key": "ssaoFilterRadius",
+ "label": "SSAO Filter Radius",
+ "decimal": 0,
+ "minimum": 1,
+ "maximum": 32
+ },
+ {
+ "type": "number",
+ "key": "ssaoSamples",
+ "label": "SSAO Samples",
+ "decimal": 0,
+ "minimum": 8,
+ "maximum": 32
+ },
+ {
+ "type": "splitter"
+ },
+ {
+ "type": "boolean",
+ "key": "fogging",
+ "label": "Enable Hardware Fog"
+ },
+ {
+ "type": "enum",
+ "key": "hwFogFalloff",
+ "label": "Hardware Falloff",
+ "enum_items": [
+ { "0": "Linear"},
+ { "1": "Exponential"},
+ { "2": "Exponential Squared"}
+ ]
+ },
+ {
+ "type": "number",
+ "key": "hwFogDensity",
+ "label": "Fog Density",
+ "decimal": 2,
+ "minimum": 0,
+ "maximum": 1
+ },
+ {
+ "type": "number",
+ "key": "hwFogStart",
+ "label": "Fog Start"
+ },
+ {
+ "type": "number",
+ "key": "hwFogEnd",
+ "label": "Fog End"
+ },
+ {
+ "type": "number",
+ "key": "hwFogAlpha",
+ "label": "Fog Alpha"
+ },
+ {
+ "type": "number",
+ "key": "hwFogColorR",
+ "label": "Fog Color R",
+ "decimal": 2,
+ "minimum": 0,
+ "maximum": 1
+ },
+ {
+ "type": "number",
+ "key": "hwFogColorG",
+ "label": "Fog Color G",
+ "decimal": 2,
+ "minimum": 0,
+ "maximum": 1
+ },
+ {
+ "type": "number",
+ "key": "hwFogColorB",
+ "label": "Fog Color B",
+ "decimal": 2,
+ "minimum": 0,
+ "maximum": 1
+ },
+ {
+ "type": "splitter"
+ },
+ {
+ "type": "boolean",
+ "key": "motionBlurEnable",
+ "label": "Enable Motion Blur"
+ },
+ {
+ "type": "number",
+ "key": "motionBlurSampleCount",
+ "label": "Motion Blur Sample Count",
+ "decimal": 0,
+ "minimum": 8,
+ "maximum": 32
+ },
+ {
+ "type": "number",
+ "key": "motionBlurShutterOpenFraction",
+ "label": "Shutter Open Fraction",
+ "decimal": 3,
+ "minimum": 0.01,
+ "maximum": 32
+ },
{
"type": "splitter"
},
diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json
index 84182973a1..53247f6bd4 100644
--- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json
+++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_publish.json
@@ -107,6 +107,11 @@
"key": "enabled",
"label": "Enabled"
},
+ {
+ "type": "boolean",
+ "key": "optional",
+ "label": "Optional"
+ },
{
"type": "label",
"label": "Shader name regex can use named capture group asset to validate against current asset name.Example:
^.*(?P=<asset>.+)_SHD
"
@@ -159,6 +164,11 @@
"key": "enabled",
"label": "Enabled"
},
+ {
+ "type": "boolean",
+ "key": "optional",
+ "label": "Optional"
+ },
{
"type": "boolean",
"key": "whitelist_native_plugins",
@@ -246,6 +256,11 @@
"key": "enabled",
"label": "Enabled"
},
+ {
+ "type": "boolean",
+ "key": "optional",
+ "label": "Optional"
+ },
{
"type": "boolean",
"key": "validate_mesh",
@@ -332,6 +347,72 @@
}
]
},
+ {
+ "type": "schema_template",
+ "name": "template_publish_plugin",
+ "template_data": [
+ {
+ "key": "ValidateCurrentRenderLayerIsRenderable",
+ "label": "Validate Current Render Layer Has Renderable Camera"
+ },
+ {
+ "key": "ValidateRenderImageRule",
+ "label": "Validate Images File Rule (Workspace)"
+ },
+ {
+ "key": "ValidateRenderNoDefaultCameras",
+ "label": "Validate No Default Cameras Renderable"
+ },
+ {
+ "key": "ValidateRenderSingleCamera",
+ "label": "Validate Render Single Camera"
+ },
+ {
+ "key": "ValidateRenderLayerAOVs",
+ "label": "Validate Render Passes / AOVs Are Registered"
+ },
+ {
+ "key": "ValidateStepSize",
+ "label": "Validate Step Size"
+ },
+ {
+ "key": "ValidateVRayDistributedRendering",
+ "label": "VRay Distributed Rendering"
+ },
+ {
+ "key": "ValidateVrayReferencedAOVs",
+ "label": "VRay Referenced AOVs"
+ },
+ {
+ "key": "ValidateVRayTranslatorEnabled",
+ "label": "VRay Translator Settings"
+ },
+ {
+ "key": "ValidateVrayProxy",
+ "label": "VRay Proxy Settings"
+ },
+ {
+ "key": "ValidateVrayProxyMembers",
+ "label": "VRay Proxy Members"
+ },
+ {
+ "key": "ValidateYetiRenderScriptCallbacks",
+ "label": "Yeti Render Script Callbacks"
+ },
+ {
+ "key": "ValidateYetiRigCacheState",
+ "label": "Yeti Rig Cache State"
+ },
+ {
+ "key": "ValidateYetiRigInputShapesInInstance",
+ "label": "Yeti Rig Input Shapes In Instance"
+ },
+ {
+ "key": "ValidateYetiRigSettings",
+ "label": "Yeti Rig Settings"
+ }
+ ]
+ },
{
"type": "collapsible-wrap",
"label": "Model",
@@ -416,6 +497,11 @@
"key": "enabled",
"label": "Enabled"
},
+ {
+ "type": "boolean",
+ "key": "optional",
+ "label": "Optional"
+ },
{
"type": "label",
"label": "Validates transform suffix based on the type of its children shapes."
@@ -472,6 +558,14 @@
"key": "ValidateMeshNonManifold",
"label": "ValidateMeshNonManifold"
},
+ {
+ "key": "ValidateMeshNoNegativeScale",
+ "label": "Validate Mesh No Negative Scale"
+ },
+ {
+ "key": "ValidateMeshNonZeroEdgeLength",
+ "label": "Validate Mesh Edge Length Non Zero"
+ },
{
"key": "ValidateMeshNormalsUnlocked",
"label": "ValidateMeshNormalsUnlocked"
@@ -525,6 +619,18 @@
{
"key": "ValidateUniqueNames",
"label": "ValidateUniqueNames"
+ },
+ {
+ "key": "ValidateNoVRayMesh",
+ "label": "Validate No V-Ray Proxies (VRayMesh)"
+ },
+ {
+ "key": "ValidateUnrealMeshTriangulated",
+ "label": "Validate if Mesh is Triangulated"
+ },
+ {
+ "key": "ValidateAlembicVisibleOnly",
+ "label": "Validate Alembic visible node"
}
]
},
@@ -573,6 +679,26 @@
{
"key": "ValidateRigControllers",
"label": "Validate Rig Controllers"
+ },
+ {
+ "key": "ValidateAnimationContent",
+ "label": "Validate Animation Content"
+ },
+ {
+ "key": "ValidateOutRelatedNodeIds",
+ "label": "Validate Animation Out Set Related Node Ids"
+ },
+ {
+ "key": "ValidateRigControllersArnoldAttributes",
+ "label": "Validate Rig Controllers (Arnold Attributes)"
+ },
+ {
+ "key": "ValidateSkeletalMeshHierarchy",
+ "label": "Validate Skeletal Mesh Top Node"
+ },
+ {
+ "key": "ValidateSkinclusterDeformerSet",
+ "label": "Validate Skincluster Deformer Relationships"
}
]
},
@@ -589,6 +715,11 @@
"key": "enabled",
"label": "Enabled"
},
+ {
+ "type": "boolean",
+ "key": "optional",
+ "label": "Optional"
+ },
{
"type": "boolean",
"key": "allow_history_only",
@@ -611,9 +742,33 @@
"key": "ValidateAssemblyName",
"label": "Validate Assembly Name"
},
+ {
+ "key": "ValidateAssemblyNamespaces",
+ "label": "Validate Assembly Namespaces"
+ },
+ {
+ "key": "ValidateAssemblyModelTransforms",
+ "label": "Validate Assembly Model Transforms"
+ },
{
"key": "ValidateAssRelativePaths",
"label": "ValidateAssRelativePaths"
+ },
+ {
+ "key": "ValidateInstancerContent",
+ "label": "Validate Instancer Content"
+ },
+ {
+ "key": "ValidateInstancerFrameRanges",
+ "label": "Validate Instancer Cache Frame Ranges"
+ },
+ {
+ "key": "ValidateNoDefaultCameras",
+ "label": "Validate No Default Cameras"
+ },
+ {
+ "key": "ValidateUnrealUpAxis",
+ "label": "Validate Unreal Up-Axis check"
}
]
},
diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json
index 5bd8337e4c..805424c632 100644
--- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json
+++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json
@@ -11,10 +11,52 @@
{
"key": "LoadImage",
"label": "Image Loader"
+ }
+ ]
+ },
+ {
+ "type": "dict",
+ "collapsible": true,
+ "key": "LoadClip",
+ "label": "Clip Loader",
+ "checkbox_key": "enabled",
+ "children": [
+ {
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
},
{
- "key": "LoadClip",
- "label": "Clip Loader"
+ "type": "list",
+ "key": "_representations",
+ "label": "Representations",
+ "object_type": "text"
+ },
+ {
+ "type": "text",
+ "key": "node_name_template",
+ "label": "Node name template"
+ },
+ {
+ "type": "splitter"
+ },
+ {
+ "type": "dict",
+ "collapsible": false,
+ "key": "options_defaults",
+ "label": "Loader option defaults",
+ "children": [
+ {
+ "type": "boolean",
+ "key": "start_at_workfile",
+ "label": "Start at worfile beggining"
+ },
+ {
+ "type": "boolean",
+ "key": "add_retime",
+ "label": "Add retime"
+ }
+ ]
}
]
}
diff --git a/openpype/tests/test_lib_restructuralization.py b/openpype/tests/test_lib_restructuralization.py
index 94080e550d..ccccc76a08 100644
--- a/openpype/tests/test_lib_restructuralization.py
+++ b/openpype/tests/test_lib_restructuralization.py
@@ -21,7 +21,6 @@ def test_backward_compatibility(printer):
from openpype.lib import is_latest
from openpype.lib import any_outdated
from openpype.lib import get_asset
- from openpype.lib import get_hierarchy
from openpype.lib import get_linked_assets
from openpype.lib import get_latest_version
from openpype.lib import get_ffprobe_streams
diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py
index 915fb7f32e..b48bb61386 100644
--- a/openpype/tools/publisher/control.py
+++ b/openpype/tools/publisher/control.py
@@ -154,15 +154,20 @@ class PublishReport:
self._all_instances_by_id = {}
self._current_context = None
- def reset(self, context, publish_discover_result=None):
+ def reset(self, context, create_context):
"""Reset report and clear all data."""
- self._publish_discover_result = publish_discover_result
+
+ self._publish_discover_result = create_context.publish_discover_result
self._plugin_data = []
self._plugin_data_with_plugin = []
self._current_plugin_data = {}
self._all_instances_by_id = {}
self._current_context = context
+ for plugin in create_context.publish_plugins_mismatch_targets:
+ plugin_data = self._add_plugin_data_item(plugin)
+ plugin_data["skipped"] = True
+
def add_plugin_iter(self, plugin, context):
"""Add report about single iteration of plugin."""
for instance in context:
@@ -205,6 +210,7 @@ class PublishReport:
"name": plugin.__name__,
"label": label,
"order": plugin.order,
+ "targets": list(plugin.targets),
"instances_data": [],
"actions_data": [],
"skipped": False,
@@ -569,6 +575,8 @@ class PublisherController:
# Stop publishing
self.stop_publish()
+ self.save_changes()
+
# Reset avalon context
self.create_context.reset_avalon_context()
@@ -777,10 +785,7 @@ class PublisherController:
# - pop the key after first collector using it would be safest option?
self._publish_context.data["create_context"] = self.create_context
- self._publish_report.reset(
- self._publish_context,
- self.create_context.publish_discover_result
- )
+ self._publish_report.reset(self._publish_context, self.create_context)
self._publish_validation_errors = []
self._publish_current_plugin_validation_errors = None
self._publish_error = None
diff --git a/openpype/tools/publisher/publish_report_viewer/model.py b/openpype/tools/publisher/publish_report_viewer/model.py
index a88129a358..bd03376c55 100644
--- a/openpype/tools/publisher/publish_report_viewer/model.py
+++ b/openpype/tools/publisher/publish_report_viewer/model.py
@@ -1,4 +1,5 @@
import uuid
+import html
from Qt import QtCore, QtGui
import pyblish.api
@@ -45,7 +46,8 @@ class InstancesModel(QtGui.QStandardItemModel):
all_removed = True
for instance_item in instance_items:
item = QtGui.QStandardItem(instance_item.label)
- item.setData(instance_item.label, ITEM_LABEL_ROLE)
+ instance_label = html.escape(instance_item.label)
+ item.setData(instance_label, ITEM_LABEL_ROLE)
item.setData(instance_item.errored, ITEM_ERRORED_ROLE)
item.setData(instance_item.id, ITEM_ID_ROLE)
item.setData(instance_item.removed, INSTANCE_REMOVED_ROLE)
diff --git a/openpype/tools/publisher/publish_report_viewer/report_items.py b/openpype/tools/publisher/publish_report_viewer/report_items.py
index b47d14da25..8a01569723 100644
--- a/openpype/tools/publisher/publish_report_viewer/report_items.py
+++ b/openpype/tools/publisher/publish_report_viewer/report_items.py
@@ -83,10 +83,8 @@ class PublishReport:
logs = []
plugins_items_by_id = {}
- plugins_id_order = []
for plugin_data in data["plugins_data"]:
item = PluginItem(plugin_data)
- plugins_id_order.append(item.id)
plugins_items_by_id[item.id] = item
for instance_data_item in plugin_data["instances_data"]:
instance_id = instance_data_item["id"]
@@ -95,6 +93,14 @@ class PublishReport:
copy.deepcopy(log_item_data), item.id, instance_id
)
logs.append(log_item)
+ sorted_plugins = sorted(
+ plugins_items_by_id.values(),
+ key=lambda item: item.order
+ )
+ plugins_id_order = [
+ plugin_item.id
+ for plugin_item in sorted_plugins
+ ]
logs_by_instance_id = collections.defaultdict(list)
for log_item in logs:
diff --git a/openpype/tools/publisher/publish_report_viewer/widgets.py b/openpype/tools/publisher/publish_report_viewer/widgets.py
index fd226ea0e4..61eb814a56 100644
--- a/openpype/tools/publisher/publish_report_viewer/widgets.py
+++ b/openpype/tools/publisher/publish_report_viewer/widgets.py
@@ -1,3 +1,4 @@
+from math import ceil
from Qt import QtWidgets, QtCore, QtGui
from openpype.widgets.nice_checkbox import NiceCheckbox
@@ -137,13 +138,75 @@ class PluginLoadReportWidget(QtWidgets.QWidget):
self._model.set_report(report)
+class ZoomPlainText(QtWidgets.QPlainTextEdit):
+ def __init__(self, *args, **kwargs):
+ super(ZoomPlainText, self).__init__(*args, **kwargs)
+
+ anim_timer = QtCore.QTimer()
+ anim_timer.setInterval(20)
+
+ anim_timer.timeout.connect(self._scaling_callback)
+
+ self._anim_timer = anim_timer
+ self._zoom_enabled = False
+ self._scheduled_scalings = 0
+ self._point_size = None
+
+ def wheelEvent(self, event):
+ if not self._zoom_enabled:
+ super(ZoomPlainText, self).wheelEvent(event)
+ return
+
+ degrees = float(event.delta()) / 8
+ steps = int(ceil(degrees / 5))
+ self._scheduled_scalings += steps
+ if (self._scheduled_scalings * steps < 0):
+ self._scheduled_scalings = steps
+
+ self._anim_timer.start()
+
+ def _scaling_callback(self):
+ if self._scheduled_scalings == 0:
+ self._anim_timer.stop()
+ return
+
+ factor = 1.0 + (self._scheduled_scalings / 300)
+ font = self.font()
+ if self._point_size is None:
+ self._point_size = font.pointSizeF()
+
+ self._point_size *= factor
+ if self._point_size < 1:
+ self._point_size = 1.0
+
+ font.setPointSizeF(self._point_size)
+ # Using 'self.setFont(font)' would not be propagated when stylesheets
+ # are applied on this widget
+ self.setStyleSheet("font-size: {}pt".format(font.pointSize()))
+
+ if self._scheduled_scalings > 0:
+ self._scheduled_scalings -= 1
+ else:
+ self._scheduled_scalings += 1
+
+ def keyPressEvent(self, event):
+ if event.key() == QtCore.Qt.Key_Control:
+ self._zoom_enabled = True
+ super(ZoomPlainText, self).keyPressEvent(event)
+
+ def keyReleaseEvent(self, event):
+ if event.key() == QtCore.Qt.Key_Control:
+ self._zoom_enabled = False
+ super(ZoomPlainText, self).keyReleaseEvent(event)
+
+
class DetailsWidget(QtWidgets.QWidget):
def __init__(self, parent):
super(DetailsWidget, self).__init__(parent)
- output_widget = QtWidgets.QPlainTextEdit(self)
- output_widget.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
+ output_widget = ZoomPlainText(self)
output_widget.setObjectName("PublishLogConsole")
+ output_widget.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
diff --git a/openpype/tools/publisher/widgets/card_view_widgets.py b/openpype/tools/publisher/widgets/card_view_widgets.py
index 086cd5c59c..bd591138f4 100644
--- a/openpype/tools/publisher/widgets/card_view_widgets.py
+++ b/openpype/tools/publisher/widgets/card_view_widgets.py
@@ -22,6 +22,7 @@ Only one item can be selected at a time.
import re
import collections
+import html
from Qt import QtWidgets, QtCore
@@ -98,6 +99,7 @@ class GroupWidget(QtWidgets.QWidget):
instances(list): List of instances in
CreateContext.
"""
+
# Store instances by id and by subset name
instances_by_id = {}
instances_by_subset_name = collections.defaultdict(list)
@@ -142,6 +144,7 @@ class GroupWidget(QtWidgets.QWidget):
class CardWidget(BaseClickableFrame):
"""Clickable card used as bigger button."""
+
selected = QtCore.Signal(str, str)
# Group identifier of card
# - this must be set because if send when mouse is released with card id
@@ -178,6 +181,7 @@ class ContextCardWidget(CardWidget):
Is not visually under group widget and is always at the top of card view.
"""
+
def __init__(self, parent):
super(ContextCardWidget, self).__init__(parent)
@@ -204,13 +208,14 @@ class ContextCardWidget(CardWidget):
class InstanceCardWidget(CardWidget):
"""Card widget representing instance."""
+
active_changed = QtCore.Signal()
def __init__(self, instance, group_icon, parent):
super(InstanceCardWidget, self).__init__(parent)
self._id = instance.id
- self._group_identifier = instance.creator_label
+ self._group_identifier = instance.group_label
self._group_icon = group_icon
self.instance = instance
@@ -303,13 +308,14 @@ class InstanceCardWidget(CardWidget):
self._last_variant = variant
self._last_subset_name = subset_name
# Make `variant` bold
- found_parts = set(re.findall(variant, subset_name, re.IGNORECASE))
+ label = html.escape(self.instance.label)
+ found_parts = set(re.findall(variant, label, re.IGNORECASE))
if found_parts:
for part in found_parts:
replacement = "{}".format(part)
- subset_name = subset_name.replace(part, replacement)
+ label = label.replace(part, replacement)
- self._label_widget.setText(subset_name)
+ self._label_widget.setText(label)
# HTML text will cause that label start catch mouse clicks
# - disabling with changing interaction flag
self._label_widget.setTextInteractionFlags(
@@ -435,7 +441,7 @@ class InstanceCardView(AbstractInstanceView):
instances_by_group = collections.defaultdict(list)
identifiers_by_group = collections.defaultdict(set)
for instance in self.controller.instances:
- group_name = instance.creator_label
+ group_name = instance.group_label
instances_by_group[group_name].append(instance)
identifiers_by_group[group_name].add(
instance.creator_identifier
diff --git a/openpype/tools/publisher/widgets/create_dialog.py b/openpype/tools/publisher/widgets/create_dialog.py
index 3a68835dc7..d4740b2493 100644
--- a/openpype/tools/publisher/widgets/create_dialog.py
+++ b/openpype/tools/publisher/widgets/create_dialog.py
@@ -342,7 +342,9 @@ class CreateDialog(QtWidgets.QDialog):
creators_view = QtWidgets.QListView(self)
creators_model = QtGui.QStandardItemModel()
- creators_view.setModel(creators_model)
+ creators_sort_model = QtCore.QSortFilterProxyModel()
+ creators_sort_model.setSourceModel(creators_model)
+ creators_view.setModel(creators_sort_model)
variant_widget = VariantInputsWidget(self)
@@ -465,7 +467,7 @@ class CreateDialog(QtWidgets.QDialog):
desc_width_anim_timer = QtCore.QTimer()
desc_width_anim_timer.setInterval(10)
- prereq_timer.timeout.connect(self._on_prereq_timer)
+ prereq_timer.timeout.connect(self._invalidate_prereq)
desc_width_anim_timer.timeout.connect(self._on_desc_animation)
@@ -513,9 +515,10 @@ class CreateDialog(QtWidgets.QDialog):
self.variant_hints_group = variant_hints_group
self._creators_header_widget = creators_header_widget
- self.creators_model = creators_model
- self.creators_view = creators_view
- self.create_btn = create_btn
+ self._creators_model = creators_model
+ self._creators_sort_model = creators_sort_model
+ self._creators_view = creators_view
+ self._create_btn = create_btn
self._creator_short_desc_widget = creator_short_desc_widget
self._pre_create_widget = pre_create_widget
@@ -573,7 +576,10 @@ class CreateDialog(QtWidgets.QDialog):
def _set_context_enabled(self, enabled):
self._assets_widget.set_enabled(enabled)
self._tasks_widget.set_enabled(enabled)
+ check_prereq = self._context_widget.isEnabled() != enabled
self._context_widget.setEnabled(enabled)
+ if check_prereq:
+ self._invalidate_prereq()
def refresh(self):
# Get context before refresh to keep selection of asset and
@@ -600,23 +606,28 @@ class CreateDialog(QtWidgets.QDialog):
self._tasks_widget.set_asset_name(asset_name)
self._tasks_widget.select_task_name(task_name)
- self._invalidate_prereq()
+ self._invalidate_prereq_deffered()
- def _invalidate_prereq(self):
+ def _invalidate_prereq_deffered(self):
self._prereq_timer.start()
def _on_asset_filter_height_change(self, height):
self._creators_header_widget.setMinimumHeight(height)
self._creators_header_widget.setMaximumHeight(height)
- def _on_prereq_timer(self):
+ def _invalidate_prereq(self):
prereq_available = True
creator_btn_tooltips = []
- if self.creators_model.rowCount() < 1:
+
+ available_creators = self._creators_model.rowCount() > 0
+ if available_creators != self._creators_view.isEnabled():
+ self._creators_view.setEnabled(available_creators)
+
+ if not available_creators:
prereq_available = False
creator_btn_tooltips.append("Creator is not selected")
- if self._asset_doc is None:
+ if self._context_change_is_enabled() and self._asset_doc is None:
# QUESTION how to handle invalid asset?
prereq_available = False
creator_btn_tooltips.append("Context is not selected")
@@ -624,15 +635,15 @@ class CreateDialog(QtWidgets.QDialog):
if prereq_available != self._prereq_available:
self._prereq_available = prereq_available
- self.create_btn.setEnabled(prereq_available)
- self.creators_view.setEnabled(prereq_available)
+ self._create_btn.setEnabled(prereq_available)
+
self.variant_input.setEnabled(prereq_available)
self.variant_hints_btn.setEnabled(prereq_available)
tooltip = ""
if creator_btn_tooltips:
tooltip = "\n".join(creator_btn_tooltips)
- self.create_btn.setToolTip(tooltip)
+ self._create_btn.setToolTip(tooltip)
self._on_variant_change()
@@ -670,8 +681,8 @@ class CreateDialog(QtWidgets.QDialog):
# Refresh creators and add their families to list
existing_items = {}
old_creators = set()
- for row in range(self.creators_model.rowCount()):
- item = self.creators_model.item(row, 0)
+ for row in range(self._creators_model.rowCount()):
+ item = self._creators_model.item(row, 0)
identifier = item.data(CREATOR_IDENTIFIER_ROLE)
existing_items[identifier] = item
old_creators.add(identifier)
@@ -688,7 +699,7 @@ class CreateDialog(QtWidgets.QDialog):
item.setFlags(
QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
)
- self.creators_model.appendRow(item)
+ self._creators_model.appendRow(item)
label = creator.label or identifier
item.setData(label, QtCore.Qt.DisplayRole)
@@ -698,16 +709,17 @@ class CreateDialog(QtWidgets.QDialog):
# Remove families that are no more available
for identifier in (old_creators - new_creators):
item = existing_items[identifier]
- self.creators_model.takeRow(item.row())
+ self._creators_model.takeRow(item.row())
- if self.creators_model.rowCount() < 1:
+ if self._creators_model.rowCount() < 1:
return
+ self._creators_sort_model.sort(0)
# Make sure there is a selection
- indexes = self.creators_view.selectedIndexes()
+ indexes = self._creators_view.selectedIndexes()
if not indexes:
- index = self.creators_model.index(0, 0)
- self.creators_view.setCurrentIndex(index)
+ index = self._creators_sort_model.index(0, 0)
+ self._creators_view.setCurrentIndex(index)
else:
index = indexes[0]
@@ -726,11 +738,11 @@ class CreateDialog(QtWidgets.QDialog):
asset_name = self._assets_widget.get_selected_asset_name()
self._tasks_widget.set_asset_name(asset_name)
if self._context_change_is_enabled():
- self._invalidate_prereq()
+ self._invalidate_prereq_deffered()
def _on_task_change(self):
if self._context_change_is_enabled():
- self._invalidate_prereq()
+ self._invalidate_prereq_deffered()
def _on_current_session_context_request(self):
self._assets_widget.set_current_session_asset()
@@ -1010,13 +1022,18 @@ class CreateDialog(QtWidgets.QDialog):
if variant_value is None:
variant_value = self.variant_input.text()
- self.create_btn.setEnabled(True)
if not self._compiled_name_pattern.match(variant_value):
- self.create_btn.setEnabled(False)
+ self._create_btn.setEnabled(False)
self._set_variant_state_property("invalid")
self.subset_name_input.setText("< Invalid variant >")
return
+ if not self._context_change_is_enabled():
+ self._create_btn.setEnabled(True)
+ self._set_variant_state_property("")
+ self.subset_name_input.setText("< Valid variant >")
+ return
+
project_name = self.controller.project_name
task_name = self._get_task_name()
@@ -1027,13 +1044,14 @@ class CreateDialog(QtWidgets.QDialog):
variant_value, task_name, asset_doc, project_name
)
except TaskNotSetError:
- self.create_btn.setEnabled(False)
+ self._create_btn.setEnabled(False)
self._set_variant_state_property("invalid")
self.subset_name_input.setText("< Missing task >")
return
self.subset_name_input.setText(subset_name)
+ self._create_btn.setEnabled(True)
self._validate_subset_name(subset_name, variant_value)
def _validate_subset_name(self, subset_name, variant_value):
@@ -1088,8 +1106,8 @@ class CreateDialog(QtWidgets.QDialog):
self._set_variant_state_property(property_value)
variant_is_valid = variant_value.strip() != ""
- if variant_is_valid != self.create_btn.isEnabled():
- self.create_btn.setEnabled(variant_is_valid)
+ if variant_is_valid != self._create_btn.isEnabled():
+ self._create_btn.setEnabled(variant_is_valid)
def _set_variant_state_property(self, state):
current_value = self.variant_input.property("state")
@@ -1134,21 +1152,27 @@ class CreateDialog(QtWidgets.QDialog):
self._update_help_btn()
def _on_create(self):
- indexes = self.creators_view.selectedIndexes()
+ indexes = self._creators_view.selectedIndexes()
if not indexes or len(indexes) > 1:
return
- if not self.create_btn.isEnabled():
+ if not self._create_btn.isEnabled():
return
index = indexes[0]
creator_label = index.data(QtCore.Qt.DisplayRole)
creator_identifier = index.data(CREATOR_IDENTIFIER_ROLE)
family = index.data(FAMILY_ROLE)
- subset_name = self.subset_name_input.text()
variant = self.variant_input.text()
- asset_name = self._get_asset_name()
- task_name = self._get_task_name()
+ # Care about subset name only if context change is enabled
+ subset_name = None
+ asset_name = None
+ task_name = None
+ if self._context_change_is_enabled():
+ subset_name = self.subset_name_input.text()
+ asset_name = self._get_asset_name()
+ task_name = self._get_task_name()
+
pre_create_data = self._pre_create_widget.current_value()
# Where to define these data?
# - what data show be stored?
diff --git a/openpype/tools/publisher/widgets/list_view_widgets.py b/openpype/tools/publisher/widgets/list_view_widgets.py
index 6bddaf66c8..3e4fd5b72d 100644
--- a/openpype/tools/publisher/widgets/list_view_widgets.py
+++ b/openpype/tools/publisher/widgets/list_view_widgets.py
@@ -23,6 +23,7 @@ selection can be enabled disabled using checkbox or keyboard key presses:
```
"""
import collections
+import html
from Qt import QtWidgets, QtCore, QtGui
@@ -113,7 +114,9 @@ class InstanceListItemWidget(QtWidgets.QWidget):
self.instance = instance
- subset_name_label = QtWidgets.QLabel(instance["subset"], self)
+ instance_label = html.escape(instance.label)
+
+ subset_name_label = QtWidgets.QLabel(instance_label, self)
subset_name_label.setObjectName("ListViewSubsetName")
active_checkbox = NiceCheckbox(parent=self)
@@ -132,7 +135,7 @@ class InstanceListItemWidget(QtWidgets.QWidget):
active_checkbox.stateChanged.connect(self._on_active_change)
- self._subset_name_label = subset_name_label
+ self._instance_label_widget = subset_name_label
self._active_checkbox = active_checkbox
self._has_valid_context = None
@@ -146,8 +149,8 @@ class InstanceListItemWidget(QtWidgets.QWidget):
state = ""
if not valid:
state = "invalid"
- self._subset_name_label.setProperty("state", state)
- self._subset_name_label.style().polish(self._subset_name_label)
+ self._instance_label_widget.setProperty("state", state)
+ self._instance_label_widget.style().polish(self._instance_label_widget)
def is_active(self):
"""Instance is activated."""
@@ -176,9 +179,9 @@ class InstanceListItemWidget(QtWidgets.QWidget):
def update_instance_values(self):
"""Update instance data propagated to widgets."""
# Check subset name
- subset_name = self.instance["subset"]
- if subset_name != self._subset_name_label.text():
- self._subset_name_label.setText(subset_name)
+ label = self.instance.label
+ if label != self._instance_label_widget.text():
+ self._instance_label_widget.setText(html.escape(label))
# Check active state
self.set_active(self.instance["active"])
# Check valid states
@@ -519,7 +522,7 @@ class InstanceListView(AbstractInstanceView):
instances_by_group_name = collections.defaultdict(list)
group_names = set()
for instance in self.controller.instances:
- group_label = instance.creator_label
+ group_label = instance.group_label
group_names.add(group_label)
instances_by_group_name[group_label].append(instance)
diff --git a/openpype/tools/publisher/widgets/widgets.py b/openpype/tools/publisher/widgets/widgets.py
index 7096b9fb50..5a5f8c4c37 100644
--- a/openpype/tools/publisher/widgets/widgets.py
+++ b/openpype/tools/publisher/widgets/widgets.py
@@ -1225,6 +1225,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget):
different creators. If creator have same (similar) definitions their
widgets are merged into one (different label does not count).
"""
+
def __init__(self, controller, parent):
super(CreatorAttrsWidget, self).__init__(parent)
@@ -1275,6 +1276,7 @@ class CreatorAttrsWidget(QtWidgets.QWidget):
content_layout = QtWidgets.QGridLayout(content_widget)
content_layout.setColumnStretch(0, 0)
content_layout.setColumnStretch(1, 1)
+ content_layout.setAlignment(QtCore.Qt.AlignTop)
row = 0
for attr_def, attr_instances, values in result:
diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py
index 764f42f1a3..f42027d9e2 100644
--- a/openpype/tools/settings/settings/categories.py
+++ b/openpype/tools/settings/settings/categories.py
@@ -854,6 +854,9 @@ class ProjectWidget(SettingsCategoryWidget):
project_list_widget.version_change_requested.connect(
self._on_source_version_change
)
+ project_list_widget.extract_to_file_requested.connect(
+ self._on_extract_to_file
+ )
self.project_list_widget = project_list_widget
diff --git a/openpype/tools/settings/settings/widgets.py b/openpype/tools/settings/settings/widgets.py
index 45c21d5685..88d923c16a 100644
--- a/openpype/tools/settings/settings/widgets.py
+++ b/openpype/tools/settings/settings/widgets.py
@@ -1008,6 +1008,7 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel):
class ProjectListWidget(QtWidgets.QWidget):
project_changed = QtCore.Signal()
version_change_requested = QtCore.Signal(str)
+ extract_to_file_requested = QtCore.Signal()
def __init__(self, parent, only_active=False):
self._parent = parent
@@ -1099,7 +1100,12 @@ class ProjectListWidget(QtWidgets.QWidget):
self.version_change_requested
)
submenu.addAction(action)
+
+ extract_action = QtWidgets.QAction("Extract to file", menu)
+ extract_action.triggered.connect(self.extract_to_file_requested)
+
menu.addMenu(submenu)
+ menu.addAction(extract_action)
menu.exec_(QtGui.QCursor.pos())
def on_item_clicked(self, new_index):
diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py
index 5934c4aa8a..cc33287091 100644
--- a/openpype/tools/traypublisher/window.py
+++ b/openpype/tools/traypublisher/window.py
@@ -12,9 +12,7 @@ from openpype.pipeline import (
install_host,
AvalonMongoDB,
)
-from openpype.hosts.traypublisher import (
- api as traypublisher
-)
+from openpype.hosts.traypublisher.api import TrayPublisherHost
from openpype.tools.publisher import PublisherWindow
from openpype.tools.utils.constants import PROJECT_NAME_ROLE
from openpype.tools.utils.models import (
@@ -111,9 +109,13 @@ class StandaloneOverlayWidget(QtWidgets.QFrame):
if project_name:
self._set_project(project_name)
+ @property
+ def host(self):
+ return self._publisher_window.controller.host
+
def _set_project(self, project_name):
self._project_name = project_name
- traypublisher.set_project_name(project_name)
+ self.host.set_project_name(project_name)
self.setVisible(False)
self.project_selected.emit(project_name)
@@ -190,7 +192,8 @@ class TrayPublishWindow(PublisherWindow):
def main():
- install_host(traypublisher)
+ host = TrayPublisherHost()
+ install_host(host)
app = QtWidgets.QApplication([])
window = TrayPublishWindow()
window.show()
diff --git a/openpype/tools/utils/__init__.py b/openpype/tools/utils/__init__.py
index 0f367510bd..5ccc1b40b3 100644
--- a/openpype/tools/utils/__init__.py
+++ b/openpype/tools/utils/__init__.py
@@ -1,4 +1,5 @@
from .widgets import (
+ CustomTextComboBox,
PlaceholderLineEdit,
BaseClickableFrame,
ClickableFrame,
@@ -28,6 +29,7 @@ from .overlay_messages import (
__all__ = (
+ "CustomTextComboBox",
"PlaceholderLineEdit",
"BaseClickableFrame",
"ClickableFrame",
diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py
index ae23e4d089..52d15a59f7 100644
--- a/openpype/tools/utils/host_tools.py
+++ b/openpype/tools/utils/host_tools.py
@@ -60,31 +60,14 @@ class HostToolsHelper:
return self._workfiles_tool
- def show_workfiles(self, parent=None, use_context=None, save=None):
+ def show_workfiles(
+ self, parent=None, use_context=None, save=None, on_top=None
+ ):
"""Workfiles tool for changing context and saving workfiles."""
- if use_context is None:
- use_context = True
-
- if save is None:
- save = True
with qt_app_context():
workfiles_tool = self.get_workfiles_tool(parent)
- workfiles_tool.set_save_enabled(save)
-
- if not workfiles_tool.isVisible():
- workfiles_tool.show()
-
- if use_context:
- context = {
- "asset": legacy_io.Session["AVALON_ASSET"],
- "task": legacy_io.Session["AVALON_TASK"]
- }
- workfiles_tool.set_context(context)
-
- # Pull window to the front.
- workfiles_tool.raise_()
- workfiles_tool.activateWindow()
+ workfiles_tool.ensure_visible(use_context, save, on_top)
def get_loader_tool(self, parent):
"""Create, cache and return loader tool window."""
@@ -395,9 +378,9 @@ def show_tool_by_name(tool_name, parent=None, *args, **kwargs):
_SingletonPoint.show_tool_by_name(tool_name, parent, *args, **kwargs)
-def show_workfiles(parent=None, use_context=None, save=None):
+def show_workfiles(*args, **kwargs):
_SingletonPoint.show_tool_by_name(
- "workfiles", parent, use_context=use_context, save=save
+ "workfiles", *args, **kwargs
)
diff --git a/openpype/tools/utils/widgets.py b/openpype/tools/utils/widgets.py
index d5ae909be8..df0d349822 100644
--- a/openpype/tools/utils/widgets.py
+++ b/openpype/tools/utils/widgets.py
@@ -11,6 +11,28 @@ from openpype.style import (
log = logging.getLogger(__name__)
+class CustomTextComboBox(QtWidgets.QComboBox):
+ """Combobox which can have different text showed."""
+
+ def __init__(self, *args, **kwargs):
+ self._custom_text = None
+ super(CustomTextComboBox, self).__init__(*args, **kwargs)
+
+ def set_custom_text(self, text=None):
+ if self._custom_text != text:
+ self._custom_text = text
+ self.repaint()
+
+ def paintEvent(self, event):
+ painter = QtWidgets.QStylePainter(self)
+ option = QtWidgets.QStyleOptionComboBox()
+ self.initStyleOption(option)
+ if self._custom_text is not None:
+ option.currentText = self._custom_text
+ painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, option)
+ painter.drawControl(QtWidgets.QStyle.CE_ComboBoxLabel, option)
+
+
class PlaceholderLineEdit(QtWidgets.QLineEdit):
"""Set placeholder color of QLineEdit in Qt 5.12 and higher."""
def __init__(self, *args, **kwargs):
diff --git a/openpype/tools/workfiles/window.py b/openpype/tools/workfiles/window.py
index 9f4cea2f8a..0b0d67e589 100644
--- a/openpype/tools/workfiles/window.py
+++ b/openpype/tools/workfiles/window.py
@@ -1,11 +1,15 @@
import os
import datetime
-from Qt import QtCore, QtWidgets
+from Qt import QtCore, QtWidgets, QtGui
-from openpype.client import get_asset_by_id, get_asset_by_name
+from openpype.client import (
+ get_asset_by_id,
+ get_asset_by_name,
+ get_workfile_info,
+)
from openpype import style
+from openpype import resources
from openpype.lib import (
- get_workfile_doc,
create_workfile_doc,
save_workfile_data_to_doc,
)
@@ -139,21 +143,19 @@ class SidePanelWidget(QtWidgets.QWidget):
return self._workfile_doc, data
-class Window(QtWidgets.QMainWindow):
+class Window(QtWidgets.QWidget):
"""Work Files Window"""
title = "Work Files"
def __init__(self, parent=None):
super(Window, self).__init__(parent=parent)
self.setWindowTitle(self.title)
- window_flags = QtCore.Qt.Window | QtCore.Qt.WindowCloseButtonHint
- if not parent:
- window_flags |= QtCore.Qt.WindowStaysOnTopHint
- self.setWindowFlags(window_flags)
+ icon = QtGui.QIcon(resources.get_openpype_icon_filepath())
+ self.setWindowIcon(icon)
+ self.setWindowFlags(self.windowFlags() | QtCore.Qt.Window)
# Create pages widget and set it as central widget
pages_widget = QtWidgets.QStackedWidget(self)
- self.setCentralWidget(pages_widget)
home_page_widget = QtWidgets.QWidget(pages_widget)
home_body_widget = QtWidgets.QWidget(home_page_widget)
@@ -188,6 +190,9 @@ class Window(QtWidgets.QMainWindow):
# the files widget has a filter field which tasks does not.
tasks_widget.setContentsMargins(0, 32, 0, 0)
+ main_layout = QtWidgets.QHBoxLayout(self)
+ main_layout.addWidget(pages_widget, 1)
+
# Set context after asset widget is refreshed
# - to do so it is necessary to wait until refresh is done
set_context_timer = QtCore.QTimer()
@@ -224,6 +229,49 @@ class Window(QtWidgets.QMainWindow):
self._first_show = True
self._context_to_set = None
+ def ensure_visible(
+ self, use_context=None, save=None, on_top=None
+ ):
+ if save is None:
+ save = True
+
+ self.set_save_enabled(save)
+
+ if self.isVisible():
+ use_context = False
+ elif use_context is None:
+ use_context = True
+
+ if on_top is None and self._first_show:
+ on_top = self.parent() is None
+
+ window_flags = self.windowFlags()
+ new_window_flags = window_flags
+ if on_top is True:
+ new_window_flags = window_flags | QtCore.Qt.WindowStaysOnTopHint
+ elif on_top is False:
+ new_window_flags = window_flags & ~QtCore.Qt.WindowStaysOnTopHint
+
+ if new_window_flags != window_flags:
+ # Note this is not propagated after initialization of widget in
+ # some Qt builds
+ self.setWindowFlags(new_window_flags)
+ self.show()
+
+ elif not self.isVisible():
+ self.show()
+
+ if use_context is None or use_context is True:
+ context = {
+ "asset": legacy_io.Session["AVALON_ASSET"],
+ "task": legacy_io.Session["AVALON_TASK"]
+ }
+ self.set_context(context)
+
+ # Pull window to the front.
+ self.raise_()
+ self.activateWindow()
+
@property
def project_name(self):
return legacy_io.Session["AVALON_PROJECT"]
@@ -255,8 +303,9 @@ class Window(QtWidgets.QMainWindow):
workfile_doc = None
if asset_id and task_name and filepath:
filename = os.path.split(filepath)[1]
- workfile_doc = get_workfile_doc(
- asset_id, task_name, filename, legacy_io
+ project_name = legacy_io.active_project()
+ workfile_doc = get_workfile_info(
+ project_name, asset_id, task_name, filename
)
self.side_panel.set_context(
asset_id, task_name, filepath, workfile_doc
@@ -289,8 +338,9 @@ class Window(QtWidgets.QMainWindow):
return
filename = os.path.split(filepath)[1]
- return get_workfile_doc(
- asset_id, task_name, filename, legacy_io
+ project_name = legacy_io.active_project()
+ return get_workfile_info(
+ project_name, asset_id, task_name, filename
)
def _create_workfile_doc(self, filepath, force=False):
@@ -326,6 +376,7 @@ class Window(QtWidgets.QMainWindow):
if self.assets_widget.refreshing:
return
+ self._set_context_timer.stop()
self._context_to_set, context = None, self._context_to_set
if "asset" in context:
asset_doc = get_asset_by_name(
diff --git a/openpype/vendor/python/common/capture.py b/openpype/vendor/python/common/capture.py
index 6b4c40a6e8..71b86a5f1a 100644
--- a/openpype/vendor/python/common/capture.py
+++ b/openpype/vendor/python/common/capture.py
@@ -380,7 +380,8 @@ Viewport2Options = {
"transparencyAlgorithm": 1,
"transparencyQuality": 0.33,
"useMaximumHardwareLights": True,
- "vertexAnimationCache": 0
+ "vertexAnimationCache": 0,
+ "renderDepthOfField": 0
}
@@ -402,7 +403,7 @@ def apply_view(panel, **options):
camera_options = options.get("camera_options", {})
_iteritems = getattr(camera_options, "iteritems", camera_options.items)
for key, value in _iteritems:
- cmds.setAttr("{0}.{1}".format(camera, key), value)
+ _safe_setAttr("{0}.{1}".format(camera, key), value)
# Viewport options
viewport_options = options.get("viewport_options", {})
@@ -416,7 +417,7 @@ def apply_view(panel, **options):
)
for key, value in _iteritems():
attr = "hardwareRenderingGlobals.{0}".format(key)
- cmds.setAttr(attr, value)
+ _safe_setAttr(attr, value)
def parse_active_panel():
@@ -550,10 +551,10 @@ def apply_scene(**options):
cmds.playbackOptions(maxTime=options["end_frame"])
if "width" in options:
- cmds.setAttr("defaultResolution.width", options["width"])
+ _safe_setAttr("defaultResolution.width", options["width"])
if "height" in options:
- cmds.setAttr("defaultResolution.height", options["height"])
+ _safe_setAttr("defaultResolution.height", options["height"])
if "compression" in options:
cmds.optionVar(
@@ -664,7 +665,7 @@ def _applied_camera_options(options, panel):
_iteritems = getattr(options, "iteritems", options.items)
for opt, value in _iteritems():
- cmds.setAttr(camera + "." + opt, value)
+ _safe_setAttr(camera + "." + opt, value)
try:
yield
@@ -672,7 +673,7 @@ def _applied_camera_options(options, panel):
if old_options:
_iteritems = getattr(old_options, "iteritems", old_options.items)
for opt, value in _iteritems():
- cmds.setAttr(camera + "." + opt, value)
+ _safe_setAttr(camera + "." + opt, value)
@contextlib.contextmanager
@@ -759,7 +760,7 @@ def _applied_viewport2_options(options):
# Apply settings
_iteritems = getattr(options, "iteritems", options.items)
for opt, value in _iteritems():
- cmds.setAttr("hardwareRenderingGlobals." + opt, value)
+ _safe_setAttr("hardwareRenderingGlobals." + opt, value)
try:
yield
@@ -767,7 +768,7 @@ def _applied_viewport2_options(options):
# Restore previous settings
_iteritems = getattr(original, "iteritems", original.items)
for opt, value in _iteritems():
- cmds.setAttr("hardwareRenderingGlobals." + opt, value)
+ _safe_setAttr("hardwareRenderingGlobals." + opt, value)
@contextlib.contextmanager
@@ -801,14 +802,14 @@ def _maintain_camera(panel, camera):
else:
state = dict((camera, cmds.getAttr(camera + ".rnd"))
for camera in cmds.ls(type="camera"))
- cmds.setAttr(camera + ".rnd", True)
+ _safe_setAttr(camera + ".rnd", True)
try:
yield
finally:
_iteritems = getattr(state, "iteritems", state.items)
for camera, renderable in _iteritems():
- cmds.setAttr(camera + ".rnd", renderable)
+ _safe_setAttr(camera + ".rnd", renderable)
@contextlib.contextmanager
@@ -845,6 +846,18 @@ def _in_standalone():
return not hasattr(cmds, "about") or cmds.about(batch=True)
+def _safe_setAttr(*args, **kwargs):
+ """Wrapper to handle failures when attribute is locked.
+
+ Temporary hotfix until better approach (store value, unlock, set new,
+ return old, lock again) is implemented.
+ """
+ try:
+ cmds.setAttr(*args, **kwargs)
+ except RuntimeError:
+ print("Cannot setAttr {}!".format(args))
+
+
# --------------------------------
#
# Apply version specific settings
diff --git a/openpype/version.py b/openpype/version.py
index 3cb7f4572b..dd5ad97449 100644
--- a/openpype/version.py
+++ b/openpype/version.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring Pype version."""
-__version__ = "3.12.1-nightly.3"
+__version__ = "3.12.2-nightly.2"
diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py
index 698a91a1a5..d29aa1b607 100644
--- a/openpype/widgets/attribute_defs/files_widget.py
+++ b/openpype/widgets/attribute_defs/files_widget.py
@@ -1,6 +1,7 @@
import os
import collections
import uuid
+import json
from Qt import QtWidgets, QtCore, QtGui
@@ -26,6 +27,27 @@ IS_SEQUENCE_ROLE = QtCore.Qt.UserRole + 7
EXT_ROLE = QtCore.Qt.UserRole + 8
+def convert_bytes_to_json(bytes_value):
+ if isinstance(bytes_value, QtCore.QByteArray):
+ # Raw data are already QByteArray and we don't have to load them
+ encoded_data = bytes_value
+ else:
+ encoded_data = QtCore.QByteArray.fromRawData(bytes_value)
+ stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.ReadOnly)
+ text = stream.readQString()
+ try:
+ return json.loads(text)
+ except Exception:
+ return None
+
+
+def convert_data_to_bytes(data):
+ bytes_value = QtCore.QByteArray()
+ stream = QtCore.QDataStream(bytes_value, QtCore.QIODevice.WriteOnly)
+ stream.writeQString(json.dumps(data))
+ return bytes_value
+
+
class SupportLabel(QtWidgets.QLabel):
pass
@@ -33,7 +55,7 @@ class SupportLabel(QtWidgets.QLabel):
class DropEmpty(QtWidgets.QWidget):
_empty_extensions = "Any file"
- def __init__(self, single_item, allow_sequences, parent):
+ def __init__(self, single_item, allow_sequences, extensions_label, parent):
super(DropEmpty, self).__init__(parent)
drop_label_widget = QtWidgets.QLabel("Drag & Drop files here", self)
@@ -61,7 +83,19 @@ class DropEmpty(QtWidgets.QWidget):
widget.setAlignment(QtCore.Qt.AlignCenter)
widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
+ update_size_timer = QtCore.QTimer()
+ update_size_timer.setInterval(10)
+ update_size_timer.setSingleShot(True)
+
+ update_size_timer.timeout.connect(self._on_update_size_timer)
+
+ self._update_size_timer = update_size_timer
+
+ if extensions_label and not extensions_label.startswith(" "):
+ extensions_label = " " + extensions_label
+
self._single_item = single_item
+ self._extensions_label = extensions_label
self._allow_sequences = allow_sequences
self._allowed_extensions = set()
self._allow_folders = None
@@ -114,22 +148,51 @@ class DropEmpty(QtWidgets.QWidget):
items_label = "Single "
if len(allowed_items) == 1:
- allowed_items_label = allowed_items[0]
+ extensions_label = allowed_items[0]
elif len(allowed_items) == 2:
- allowed_items_label = " or ".join(allowed_items)
+ extensions_label = " or ".join(allowed_items)
else:
last_item = allowed_items.pop(-1)
new_last_item = " or ".join(last_item, allowed_items.pop(-1))
allowed_items.append(new_last_item)
- allowed_items_label = ", ".join(allowed_items)
+ extensions_label = ", ".join(allowed_items)
+
+ allowed_items_label = extensions_label
items_label += allowed_items_label
+ label_tooltip = None
if self._allowed_extensions:
items_label += " of\n{}".format(
", ".join(sorted(self._allowed_extensions))
)
+ if self._extensions_label:
+ label_tooltip = items_label
+ items_label = self._extensions_label
+
+ if self._items_label_widget.text() == items_label:
+ return
+
+ self._items_label_widget.setToolTip(label_tooltip)
self._items_label_widget.setText(items_label)
+ self._update_size_timer.start()
+
+ def resizeEvent(self, event):
+ super(DropEmpty, self).resizeEvent(event)
+ self._update_size_timer.start()
+
+ def _on_update_size_timer(self):
+ """Recalculate height of label with extensions.
+
+ Dynamic QLabel with word wrap does not handle properly it's sizeHint
+ calculations on show. This way it is recalculated. It is good practice
+ to trigger this method with small offset using '_update_size_timer'.
+ """
+
+ width = self._items_label_widget.width()
+ height = self._items_label_widget.heightForWidth(width)
+ self._items_label_widget.setMinimumHeight(height)
+ self._items_label_widget.updateGeometry()
def paintEvent(self, event):
super(DropEmpty, self).paintEvent(event)
@@ -162,6 +225,7 @@ class FilesModel(QtGui.QStandardItemModel):
def __init__(self, single_item, allow_sequences):
super(FilesModel, self).__init__()
+ self._id = str(uuid.uuid4())
self._single_item = single_item
self._multivalue = False
self._allow_sequences = allow_sequences
@@ -171,6 +235,10 @@ class FilesModel(QtGui.QStandardItemModel):
self._filenames_by_dirpath = collections.defaultdict(set)
self._items_by_dirpath = collections.defaultdict(list)
+ @property
+ def id(self):
+ return self._id
+
def set_multivalue(self, multivalue):
"""Disable filtering."""
@@ -245,6 +313,66 @@ class FilesModel(QtGui.QStandardItemModel):
return item_id, item
+ def mimeData(self, indexes):
+ item_ids = [
+ index.data(ITEM_ID_ROLE)
+ for index in indexes
+ ]
+
+ item_ids_data = convert_data_to_bytes(item_ids)
+ mime_data = super(FilesModel, self).mimeData(indexes)
+ mime_data.setData("files_widget/internal_move", item_ids_data)
+
+ file_items = []
+ for item_id in item_ids:
+ file_item = self.get_file_item_by_id(item_id)
+ if file_item:
+ file_items.append(file_item.to_dict())
+
+ full_item_data = convert_data_to_bytes({
+ "items": file_items,
+ "id": self._id
+ })
+ mime_data.setData("files_widget/full_data", full_item_data)
+ return mime_data
+
+ def dropMimeData(self, mime_data, action, row, col, index):
+ item_ids = convert_bytes_to_json(
+ mime_data.data("files_widget/internal_move")
+ )
+ if item_ids is None:
+ return False
+
+ # Find matching item after which will be items moved
+ # - store item before moved items are removed
+ root = self.invisibleRootItem()
+ if row >= 0:
+ src_item = self.item(row)
+ else:
+ src_item_id = index.data(ITEM_ID_ROLE)
+ src_item = self._items_by_id.get(src_item_id)
+
+ # Take out items that should be moved
+ items = []
+ for item_id in item_ids:
+ item = self._items_by_id.get(item_id)
+ if item:
+ self.takeRow(item.row())
+ items.append(item)
+
+ # Skip if there are not items that can be moved
+ if not items:
+ return False
+
+ # Calculate row where items should be inserted
+ if src_item:
+ src_row = src_item.row()
+ else:
+ src_row = root.rowCount()
+
+ root.insertRow(src_row, items)
+ return True
+
class FilesProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, *args, **kwargs):
@@ -428,6 +556,9 @@ class FilesView(QtWidgets.QListView):
QtWidgets.QAbstractItemView.ExtendedSelection
)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.setAcceptDrops(True)
+ self.setDragEnabled(True)
+ self.setDragDropMode(self.InternalMove)
remove_btn = InViewButton(self)
pix_enabled = paint_image_with_color(
@@ -529,11 +660,13 @@ class FilesView(QtWidgets.QListView):
class FilesWidget(QtWidgets.QFrame):
value_changed = QtCore.Signal()
- def __init__(self, single_item, allow_sequences, parent):
+ def __init__(self, single_item, allow_sequences, extensions_label, parent):
super(FilesWidget, self).__init__(parent)
self.setAcceptDrops(True)
- empty_widget = DropEmpty(single_item, allow_sequences, self)
+ empty_widget = DropEmpty(
+ single_item, allow_sequences, extensions_label, self
+ )
files_model = FilesModel(single_item, allow_sequences)
files_proxy_model = FilesProxyModel()
@@ -553,6 +686,7 @@ class FilesWidget(QtWidgets.QFrame):
files_view.context_menu_requested.connect(
self._on_context_menu_requested
)
+
self._in_set_value = False
self._single_item = single_item
self._multivalue = False
@@ -637,8 +771,6 @@ class FilesWidget(QtWidgets.QFrame):
)
self._widgets_by_id[item_id] = widget
- self._files_proxy_model.sort(0)
-
if not self._in_set_value:
self.value_changed.emit()
@@ -743,12 +875,22 @@ class FilesWidget(QtWidgets.QFrame):
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
+ full_data_value = mime_data.data("files_widget/full_data")
+ if self._handle_full_data_drag(full_data_value):
+ event.setDropAction(QtCore.Qt.CopyAction)
+ event.accept()
+
def dragLeaveEvent(self, event):
event.accept()
def dropEvent(self, event):
+ if self._multivalue:
+ return
+
mime_data = event.mimeData()
- if not self._multivalue and mime_data.hasUrls():
+ if mime_data.hasUrls():
+ event.accept()
+ # event.setDropAction(QtCore.Qt.CopyAction)
filepaths = []
for url in mime_data.urls():
filepath = url.toLocalFile()
@@ -759,7 +901,58 @@ class FilesWidget(QtWidgets.QFrame):
filepaths = self._files_proxy_model.filter_valid_files(filepaths)
if filepaths:
self._add_filepaths(filepaths)
- event.accept()
+
+ if self._handle_full_data_drop(
+ mime_data.data("files_widget/full_data")
+ ):
+ event.setDropAction(QtCore.Qt.CopyAction)
+ event.accept()
+
+ super(FilesWidget, self).dropEvent(event)
+
+ def _handle_full_data_drag(self, value):
+ if value is None:
+ return False
+
+ full_data = convert_bytes_to_json(value)
+ if full_data is None:
+ return False
+
+ if full_data["id"] == self._files_model.id:
+ return False
+ return True
+
+ def _handle_full_data_drop(self, value):
+ if value is None:
+ return False
+
+ full_data = convert_bytes_to_json(value)
+ if full_data is None:
+ return False
+
+ if full_data["id"] == self._files_model.id:
+ return False
+
+ for item in full_data["items"]:
+ filepaths = [
+ os.path.join(item["directory"], filename)
+ for filename in item["filenames"]
+ ]
+ filepaths = self._files_proxy_model.filter_valid_files(filepaths)
+ if filepaths:
+ self._add_filepaths(filepaths)
+
+ if self._copy_modifiers_enabled():
+ return False
+ return True
+
+ def _copy_modifiers_enabled(self):
+ if (
+ QtWidgets.QApplication.keyboardModifiers()
+ & QtCore.Qt.ControlModifier
+ ):
+ return True
+ return False
def _add_filepaths(self, filepaths):
self._files_model.add_filepaths(filepaths)
diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py
index b6493b80a8..60ae952553 100644
--- a/openpype/widgets/attribute_defs/widgets.py
+++ b/openpype/widgets/attribute_defs/widgets.py
@@ -15,6 +15,7 @@ from openpype.lib.attribute_definitions import (
UISeparatorDef,
UILabelDef
)
+from openpype.tools.utils import CustomTextComboBox
from openpype.widgets.nice_checkbox import NiceCheckbox
from .files_widget import FilesWidget
@@ -369,8 +370,12 @@ class BoolAttrWidget(_BaseAttrDefWidget):
class EnumAttrWidget(_BaseAttrDefWidget):
+ def __init__(self, *args, **kwargs):
+ self._multivalue = False
+ super(EnumAttrWidget, self).__init__(*args, **kwargs)
+
def _ui_init(self):
- input_widget = QtWidgets.QComboBox(self)
+ input_widget = CustomTextComboBox(self)
combo_delegate = QtWidgets.QStyledItemDelegate(input_widget)
input_widget.setItemDelegate(combo_delegate)
@@ -394,6 +399,9 @@ class EnumAttrWidget(_BaseAttrDefWidget):
def _on_value_change(self):
new_value = self.current_value()
+ if self._multivalue:
+ self._multivalue = False
+ self._input_widget.set_custom_text(None)
self.value_changed.emit(new_value, self.attr_def.id)
def current_value(self):
@@ -401,14 +409,23 @@ class EnumAttrWidget(_BaseAttrDefWidget):
return self._input_widget.itemData(idx)
def set_value(self, value, multivalue=False):
+ if multivalue:
+ set_value = set(value)
+ if len(set_value) == 1:
+ multivalue = False
+ value = tuple(set_value)[0]
+
if not multivalue:
idx = self._input_widget.findData(value)
cur_idx = self._input_widget.currentIndex()
if idx != cur_idx and idx >= 0:
self._input_widget.setCurrentIndex(idx)
- else:
- self._input_widget.lineEdit().setText("Multiselection")
+ custom_text = None
+ if multivalue:
+ custom_text = "< Multiselection >"
+ self._input_widget.set_custom_text(custom_text)
+ self._multivalue = multivalue
class UnknownAttrWidget(_BaseAttrDefWidget):
@@ -443,7 +460,10 @@ class UnknownAttrWidget(_BaseAttrDefWidget):
class FileAttrWidget(_BaseAttrDefWidget):
def _ui_init(self):
input_widget = FilesWidget(
- self.attr_def.single_item, self.attr_def.allow_sequences, self
+ self.attr_def.single_item,
+ self.attr_def.allow_sequences,
+ self.attr_def.extensions_label,
+ self
)
if self.attr_def.tooltip:
diff --git a/pyproject.toml b/pyproject.toml
index 4803249923..9552242694 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "OpenPype"
-version = "3.12.1-nightly.3" # OpenPype
+version = "3.12.2-nightly.2" # OpenPype
description = "Open VFX and Animation pipeline with support."
authors = ["OpenPype Team "]
license = "MIT License"
@@ -135,7 +135,7 @@ hash = "b9950f5d2fa3720b52b8be55bacf5f56d33f9e029d38ee86534995f3d8d253d2"
[openpype.thirdparty.oiio.linux]
url = "https://distribute.openpype.io/thirdparty/oiio_tools-2.2.20-linux-centos7.tgz"
-hash = "be1abf8a50e9da5913298447421af0a17829d83ed6252ae1d40da7fa36a78787"
+hash = "3894dec7e4e521463891a869586850e8605f5fd604858b674c87323bf33e273d"
[openpype.thirdparty.oiio.darwin]
url = "https://distribute.openpype.io/thirdparty/oiio-2.2.0-darwin.tgz"
diff --git a/tools/build.ps1 b/tools/build.ps1
index ff28544954..195b2dc75e 100644
--- a/tools/build.ps1
+++ b/tools/build.ps1
@@ -28,6 +28,13 @@ if($arguments -eq "--no-submodule-update") {
$disable_submodule_update=$true
}
+$current_dir = Get-Location
+$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$openpype_root = (Get-Item $script_dir).parent.FullName
+
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell"
+
function Start-Progress {
param([ScriptBlock]$code)
$scroll = "/-\|/-\|"
@@ -72,14 +79,18 @@ function Exit-WithCode($exitcode) {
function Show-PSWarning() {
if ($PSVersionTable.PSVersion.Major -lt 7) {
- Write-Host "!!! " -NoNewline -ForegroundColor Red
- Write-Host "You are using old version of PowerShell. $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)"
- Write-Host "Please update to at least 7.0 - " -NoNewline -ForegroundColor Gray
- Write-Host "https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White
+ Write-Color -Text "!!! ", "You are using old version of PowerShell - ", "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" -Color Red, Yellow, White
+ Write-Color -Text " Please update to at least 7.0 - ", "https://github.com/PowerShell/PowerShell/releases" -Color Yellow, White
Exit-WithCode 1
}
}
+function Install-Poetry() {
+ Write-Color -Text ">>> ", "Installing Poetry ... " -Color Green, Gray
+ $env:POETRY_HOME="$openpype_root\.poetry"
+ (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | python -
+}
+
$art = @"
. . .. . ..
@@ -103,10 +114,6 @@ Write-Host $art -ForegroundColor DarkGreen
# Enable if PS 7.x is needed.
# Show-PSWarning
-$current_dir = Get-Location
-$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
-$openpype_root = (Get-Item $script_dir).parent.FullName
-
$env:_INSIDE_OPENPYPE_TOOL = "1"
if (-not (Test-Path 'env:POETRY_HOME')) {
@@ -119,8 +126,7 @@ $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py"
$result = [regex]::Matches($version_file, '__version__ = "(?\d+\.\d+.\d+.*)"')
$openpype_version = $result[0].Groups['version'].Value
if (-not $openpype_version) {
- Write-Host "!!! " -ForegroundColor yellow -NoNewline
- Write-Host "Cannot determine OpenPype version."
+ Write-Color -Text "!!! ", "Cannot determine OpenPype version." -Color Yellow, Gray
Exit-WithCode 1
}
@@ -129,75 +135,62 @@ if (-not (Test-Path -PathType Container -Path "$($openpype_root)\build")) {
New-Item -ItemType Directory -Force -Path "$($openpype_root)\build"
}
-Write-Host "--- " -NoNewline -ForegroundColor yellow
-Write-Host "Cleaning build directory ..."
+Write-Color -Text "--- ", "Cleaning build directory ..." -Color Yellow, Gray
try {
Remove-Item -Recurse -Force "$($openpype_root)\build\*"
}
catch {
- Write-Host "!!! " -NoNewline -ForegroundColor Red
- Write-Host "Cannot clean build directory, possibly because process is using it."
- Write-Host $_.Exception.Message
+ Write-Color -Text "!!! ", "Cannot clean build directory, possibly because process is using it." -Color Red, Gray
+ Write-Color -Text $_.Exception.Message -Color Red
Exit-WithCode 1
}
if (-not $disable_submodule_update) {
- Write-Host ">>> " -NoNewLine -ForegroundColor green
- Write-Host "Making sure submodules are up-to-date ..."
- git submodule update --init --recursive
+ Write-Color -Text ">>> ", "Making sure submodules are up-to-date ..." -Color Green, Gray
+ & git submodule update --init --recursive
} else {
- Write-Host "*** " -NoNewLine -ForegroundColor yellow
- Write-Host "Not updating submodules ..."
+ Write-Color -Text "*** ", "Not updating submodules ..." -Color Green, Gray
}
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "OpenPype [ " -NoNewline -ForegroundColor white
-Write-host $openpype_version -NoNewline -ForegroundColor green
-Write-Host " ]" -ForegroundColor white
+Write-Color -Text ">>> ", "OpenPype [ ", $openpype_version, " ]" -Color Green, White, Cyan, White
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
- Write-Host "*** " -NoNewline -ForegroundColor Yellow
- Write-Host "We need to install Poetry create virtual env first ..."
+ Write-Color -Text "NOT FOUND" -Color Yellow
+ Write-Color -Text "*** ", "We need to install Poetry create virtual env first ..." -Color Yellow, Gray
& "$openpype_root\tools\create_env.ps1"
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Cleaning cache files ... " -NoNewline
+Write-Color -Text ">>> ", "Cleaning cache files ... " -Color Green, Gray -NoNewline
Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force
Get-ChildItem $openpype_root -Filter "*.pyo" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force
Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force -Recurse
-Write-Host "OK" -ForegroundColor green
+Write-Color -Text "OK" -Color green
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Building OpenPype ..."
+Write-Color -Text ">>> ", "Building OpenPype ..." -Color Green, White
$startTime = [int][double]::Parse((Get-Date -UFormat %s))
$out = & "$($env:POETRY_HOME)\bin\poetry" run python setup.py build 2>&1
Set-Content -Path "$($openpype_root)\build\build.log" -Value $out
if ($LASTEXITCODE -ne 0)
{
- Write-Host "------------------------------------------" -ForegroundColor Red
+ Write-Color -Text "------------------------------------------" -Color Red
Get-Content "$($openpype_root)\build\build.log"
- Write-Host "------------------------------------------" -ForegroundColor Red
- Write-Host "!!! " -NoNewLine -ForegroundColor Red
- Write-Host "Build failed. Check the log: " -NoNewline
- Write-Host ".\build\build.log" -ForegroundColor Yellow
+ Write-Color -Text "------------------------------------------" -Color Yellow
+ Write-Color -Text "!!! ", "Build failed. Check the log: ", ".\build\build.log" -Color Red, Yellow, White
Exit-WithCode $LASTEXITCODE
}
Set-Content -Path "$($openpype_root)\build\build.log" -Value $out
& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\tools\build_dependencies.py"
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "restoring current directory"
+Write-Color -Text ">>> ", "Restoring current directory" -Color Green, Gray
Set-Location -Path $current_dir
$endTime = [int][double]::Parse((Get-Date -UFormat %s))
-Write-Host "*** " -NoNewline -ForegroundColor Cyan
-Write-Host "All done in $($endTime - $startTime) secs. You will find OpenPype and build log in " -NoNewLine
-Write-Host "'.\build'" -NoNewline -ForegroundColor Green
-Write-Host " directory."
+try
+{
+ New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype build complete!", "All done in $( $endTime - $startTime ) secs. You will find OpenPype and build log in build directory."
+} catch {}
+Write-Color -Text "*** ", "All done in ", $($endTime - $startTime), " secs. You will find OpenPype and build log in ", "'.\build'", " directory." -Color Green, Gray, White, Gray, White, Gray
diff --git a/tools/build_win_installer.ps1 b/tools/build_win_installer.ps1
index 49fa803742..b9d1ca2d3f 100644
--- a/tools/build_win_installer.ps1
+++ b/tools/build_win_installer.ps1
@@ -11,6 +11,12 @@
PS> .\build_win_installer.ps1
#>
+$current_dir = Get-Location
+$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$openpype_root = (Get-Item $script_dir).parent.FullName
+
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell"
function Start-Progress {
param([ScriptBlock]$code)
@@ -44,7 +50,6 @@ function Start-Progress {
#>
}
-
function Exit-WithCode($exitcode) {
# Only exit this host process if it's a child of another PowerShell parent process...
$parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId
@@ -56,10 +61,8 @@ function Exit-WithCode($exitcode) {
function Show-PSWarning() {
if ($PSVersionTable.PSVersion.Major -lt 7) {
- Write-Host "!!! " -NoNewline -ForegroundColor Red
- Write-Host "You are using old version of PowerShell. $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)"
- Write-Host "Please update to at least 7.0 - " -NoNewline -ForegroundColor Gray
- Write-Host "https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White
+ Write-Color -Text "!!! ", "You are using old version of PowerShell - ", "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" -Color Red, Yellow, White
+ Write-Color -Text " Please update to at least 7.0 - ", "https://github.com/PowerShell/PowerShell/releases" -Color Yellow, White
Exit-WithCode 1
}
}
@@ -87,9 +90,6 @@ Write-Host $art -ForegroundColor DarkGreen
# Enable if PS 7.x is needed.
# Show-PSWarning
-$current_dir = Get-Location
-$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
-$openpype_root = (Get-Item $script_dir).parent.FullName
Set-Location -Path $openpype_root
@@ -97,16 +97,15 @@ $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py"
$result = [regex]::Matches($version_file, '__version__ = "(?\d+\.\d+.\d+.*)"')
$openpype_version = $result[0].Groups['version'].Value
if (-not $openpype_version) {
- Write-Host "!!! " -ForegroundColor yellow -NoNewline
- Write-Host "Cannot determine OpenPype version."
+ Write-Color -Text "!!! ", "Cannot determine OpenPype version." -Color Yellow, Gray
Exit-WithCode 1
}
+
$env:BUILD_VERSION = $openpype_version
iscc
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Detecting host Python ... " -NoNewline
+Write-Color ">>> ", "Detecting host Python ... " -Color Green, White -NoNewline
$python = "python"
if (Get-Command "pyenv" -ErrorAction SilentlyContinue) {
$pyenv_python = & pyenv which python
@@ -115,7 +114,7 @@ if (Get-Command "pyenv" -ErrorAction SilentlyContinue) {
}
}
if (-not (Get-Command $python -ErrorAction SilentlyContinue)) {
- Write-Host "!!! Python not detected" -ForegroundColor red
+ Write-Color "!!! ", "Python not detected" -Color Red, Yellow
Set-Location -Path $current_dir
Exit-WithCode 1
}
@@ -128,7 +127,7 @@ $p = & $python -c $version_command
$env:PYTHON_VERSION = $p
$m = $p -match '(\d+)\.(\d+)'
if(-not $m) {
- Write-Host "!!! Cannot determine version" -ForegroundColor red
+ Write-Color "!!! ", "Cannot determine version" -Color Red, Yellow
Set-Location -Path $current_dir
Exit-WithCode 1
}
@@ -145,8 +144,7 @@ if (($matches[1] -lt 3) -or ($matches[2] -lt 7)) {
Write-Host "OK [ $p ]" -ForegroundColor green
}
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Creating OpenPype installer ... " -ForegroundColor white
+Write-Color -Text ">>> ", "Creating OpenPype installer ... " -Color Green, White
$build_dir_command = @"
import sys
@@ -155,24 +153,25 @@ print('exe.{}-{}'.format(get_platform(), sys.version[0:3]))
"@
$build_dir = & $python -c $build_dir_command
-Write-Host "Build directory ... ${build_dir}" -ForegroundColor white
+Write-Color -Text "--- ", "Build directory ", "${build_dir}" -Color Green, Gray, White
$env:BUILD_DIR = $build_dir
-if (Get-Command iscc -errorAction SilentlyContinue -ErrorVariable ProcessError)
-{
- iscc "$openpype_root\inno_setup.iss"
-}else {
- Write-Host "!!! Cannot find Inno Setup command" -ForegroundColor red
- Write-Host "!!! You can download it at https://jrsoftware.org/" -ForegroundColor red
+if (-not (Get-Command iscc -errorAction SilentlyContinue -ErrorVariable ProcessError)) {
+ Write-Color -Text "!!! ", "Cannot find Inno Setup command" -Color Red, Yellow
+ Write-Color "!!! You can download it at https://jrsoftware.org/" -ForegroundColor red
Exit-WithCode 1
}
+& iscc "$openpype_root\inno_setup.iss"
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "restoring current directory"
+if ($LASTEXITCODE -ne 0) {
+ Write-Color -Text "!!! ", "Creating installer failed." -Color Red, Yellow
+ Exit-WithCode 1
+}
+
+Write-Color -Text ">>> ", "Restoring current directory" -Color Green, Gray
Set-Location -Path $current_dir
-
-Write-Host "*** " -NoNewline -ForegroundColor Cyan
-Write-Host "All done. You will find OpenPype installer in " -NoNewLine
-Write-Host "'.\build'" -NoNewline -ForegroundColor Green
-Write-Host " directory."
+try {
+ New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype build complete!", "All done. You will find You will find OpenPype installer in '.\build' directory."
+} catch {}
+Write-Color -Text "*** ", "All done. You will find OpenPype installer in ", "'.\build'", " directory." -Color Green, Gray, White, Gray
diff --git a/tools/create_env.ps1 b/tools/create_env.ps1
index c307ba2031..3f956e5c6a 100644
--- a/tools/create_env.ps1
+++ b/tools/create_env.ps1
@@ -24,6 +24,15 @@ if($arguments -eq "--verbose") {
$poetry_verbosity="-vvv"
}
+$current_dir = Get-Location
+$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$openpype_root = (Get-Item $script_dir).parent.FullName
+
+& git submodule update --init --recursive
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell"
+
+
function Exit-WithCode($exitcode) {
# Only exit this host process if it's a child of another PowerShell parent process...
$parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId
@@ -36,30 +45,26 @@ function Exit-WithCode($exitcode) {
function Show-PSWarning() {
if ($PSVersionTable.PSVersion.Major -lt 7) {
- Write-Host "!!! " -NoNewline -ForegroundColor Red
- Write-Host "You are using old version of PowerShell. $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)"
- Write-Host "Please update to at least 7.0 - " -NoNewline -ForegroundColor Gray
- Write-Host "https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White
+ Write-Color -Text "!!! ", "You are using old version of PowerShell - ", "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" -Color Red, Yellow, White
+ Write-Color -Text " Please update to at least 7.0 - ", "https://github.com/PowerShell/PowerShell/releases" -Color Yellow, White
Exit-WithCode 1
}
}
function Install-Poetry() {
- Write-Host ">>> " -NoNewline -ForegroundColor Green
- Write-Host "Installing Poetry ... "
+ Write-Color -Text ">>> ", "Installing Poetry ... " -Color Green, Gray
$python = "python"
if (Get-Command "pyenv" -ErrorAction SilentlyContinue) {
if (-not (Test-Path -PathType Leaf -Path "$($openpype_root)\.python-version")) {
$result = & pyenv global
if ($result -eq "no global version configured") {
- Write-Host "!!! " -NoNewline -ForegroundColor Red
- Write-Host "Using pyenv but having no local or global version of Python set."
+ Write-Color -Text "!!! ", "Using pyenv but having no local or global version of Python set." -Color Red, Yellow
Exit-WithCode 1
}
}
$python = & pyenv which python
-
+
}
$env:POETRY_HOME="$openpype_root\.poetry"
@@ -68,8 +73,7 @@ function Install-Poetry() {
function Test-Python() {
- Write-Host ">>> " -NoNewline -ForegroundColor green
- Write-Host "Detecting host Python ... " -NoNewline
+ Write-Color -Text ">>> ", "Detecting host Python ... " -Color Green, Gray -NoNewline
$python = "python"
if (Get-Command "pyenv" -ErrorAction SilentlyContinue) {
$pyenv_python = & pyenv which python
@@ -97,22 +101,17 @@ print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))
}
# We are supporting python 3.7 only
if (($matches[1] -lt 3) -or ($matches[2] -lt 7)) {
- Write-Host "FAILED Version [ $p ] is old and unsupported" -ForegroundColor red
+ Write-Color -Text "FAILED ", "Version ", "[", $p ,"]", "is old and unsupported" -Color Red, Yellow, Cyan, White, Cyan, Yellow
Set-Location -Path $current_dir
Exit-WithCode 1
} elseif (($matches[1] -eq 3) -and ($matches[2] -gt 7)) {
- Write-Host "WARNING Version [ $p ] is unsupported, use at your own risk." -ForegroundColor yellow
- Write-Host "*** " -NoNewline -ForegroundColor yellow
- Write-Host "OpenPype supports only Python 3.7" -ForegroundColor white
+ Write-Color -Text "WARNING Version ", "[", $p, "]", " is unsupported, use at your own risk." -Color Yellow, Cyan, White, Cyan, Yellow
+ Write-Color -Text "*** ", "OpenPype supports only Python 3.7" -Color Yellow, White
} else {
- Write-Host "OK [ $p ]" -ForegroundColor green
+ Write-Color "OK ", "[", $p, "]" -Color Green, Cyan, White, Cyan
}
}
-$current_dir = Get-Location
-$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
-$openpype_root = (Get-Item $script_dir).parent.FullName
-
if (-not (Test-Path 'env:POETRY_HOME')) {
$env:POETRY_HOME = "$openpype_root\.poetry"
}
@@ -150,41 +149,39 @@ $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py"
$result = [regex]::Matches($version_file, '__version__ = "(?\d+\.\d+.\d+.*)"')
$openpype_version = $result[0].Groups['version'].Value
if (-not $openpype_version) {
- Write-Host "!!! " -ForegroundColor yellow -NoNewline
- Write-Host "Cannot determine OpenPype version."
+ Write-Color -Text "!!! ", "Cannot determine OpenPype version." -Color Red, Yellow
Set-Location -Path $current_dir
Exit-WithCode 1
}
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Found OpenPype version " -NoNewline
-Write-Host "[ $($openpype_version) ]" -ForegroundColor Green
+Write-Color -Text ">>> ", "Found OpenPype version ", "[ ", $($openpype_version), " ]" -Color Green, Gray, Cyan, White, Cyan
Test-Python
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
+ Write-Color -Text "NOT FOUND" -Color Yellow
Install-Poetry
- Write-Host "INSTALLED" -ForegroundColor Cyan
+ Write-Color -Text "INSTALLED" -Color Cyan
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
if (-not (Test-Path -PathType Leaf -Path "$($openpype_root)\poetry.lock")) {
- Write-Host ">>> " -NoNewline -ForegroundColor green
- Write-Host "Installing virtual environment and creating lock."
+ Write-Color -Text ">>> ", "Installing virtual environment and creating lock." -Color Green, Gray
} else {
- Write-Host ">>> " -NoNewline -ForegroundColor green
- Write-Host "Installing virtual environment from lock."
+ Write-Color -Text ">>> ", "Installing virtual environment from lock." -Color Green, Gray
}
+$startTime = [int][double]::Parse((Get-Date -UFormat %s))
& "$env:POETRY_HOME\bin\poetry" install --no-root $poetry_verbosity --ansi
if ($LASTEXITCODE -ne 0) {
- Write-Host "!!! " -ForegroundColor yellow -NoNewline
- Write-Host "Poetry command failed."
+ Write-Color -Text "!!! ", "Poetry command failed." -Color Red, Yellow
Set-Location -Path $current_dir
Exit-WithCode 1
}
+$endTime = [int][double]::Parse((Get-Date -UFormat %s))
Set-Location -Path $current_dir
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Virtual environment created."
+try
+{
+ New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype", "Virtual environment created.", "All done in $( $endTime - $startTime ) secs."
+} catch {}
+Write-Color -Text ">>> ", "Virtual environment created." -Color Green, White
diff --git a/tools/create_zip.ps1 b/tools/create_zip.ps1
index e33445d1fa..7b852b7c54 100644
--- a/tools/create_zip.ps1
+++ b/tools/create_zip.ps1
@@ -19,6 +19,13 @@ PS> .\create_zip.ps1 --path C:\OpenPype
#>
+$current_dir = Get-Location
+$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$openpype_root = (Get-Item $script_dir).parent.FullName
+
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell"
+
function Exit-WithCode($exitcode) {
# Only exit this host process if it's a child of another PowerShell parent process...
$parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId
@@ -31,18 +38,12 @@ function Exit-WithCode($exitcode) {
function Show-PSWarning() {
if ($PSVersionTable.PSVersion.Major -lt 7) {
- Write-Host "!!! " -NoNewline -ForegroundColor Red
- Write-Host "You are using old version of PowerShell. $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)"
- Write-Host "Please update to at least 7.0 - " -NoNewline -ForegroundColor Gray
- Write-Host "https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White
+ Write-Color -Text "!!! ", "You are using old version of PowerShell - ", "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" -Color Red, Yellow, White
+ Write-Color -Text " Please update to at least 7.0 - ", "https://github.com/PowerShell/PowerShell/releases" -Color Yellow, White
Exit-WithCode 1
}
}
-$current_dir = Get-Location
-$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
-$openpype_root = (Get-Item $script_dir).parent.FullName
-
$env:_INSIDE_OPENPYPE_TOOL = "1"
if (-not (Test-Path 'env:POETRY_HOME')) {
@@ -78,31 +79,25 @@ $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py"
$result = [regex]::Matches($version_file, '__version__ = "(?\d+\.\d+.\d+.*)"')
$openpype_version = $result[0].Groups['version'].Value
if (-not $openpype_version) {
- Write-Host "!!! " -ForegroundColor yellow -NoNewline
- Write-Host "Cannot determine OpenPype version."
+ Write-Color -Text "!!! ", "Cannot determine OpenPype version." -Color Yellow, Gray
Exit-WithCode 1
}
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
- Write-Host "*** " -NoNewline -ForegroundColor Yellow
- Write-Host "We need to install Poetry create virtual env first ..."
+ Write-Color -Text "NOT FOUND" -Color Yellow
+ Write-Color -Text "*** ", "We need to install Poetry create virtual env first ..." -Color Yellow, Gray
& "$openpype_root\tools\create_env.ps1"
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Cleaning cache files ... " -NoNewline
+Write-Color -Text ">>> ", "Cleaning cache files ... " -Color Green, Gray -NoNewline
Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse| Where-Object {( $_.FullName -inotmatch '\\build\\' ) -and ( $_.FullName -inotmatch '\\.venv' )} | Remove-Item -Force -Recurse
Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Where-Object {( $_.FullName -inotmatch '\\build\\' ) -and ( $_.FullName -inotmatch '\\.venv' )} | Remove-Item -Force
Get-ChildItem $openpype_root -Filter "*.pyo" -Force -Recurse | Where-Object {( $_.FullName -inotmatch '\\build\\' ) -and ( $_.FullName -inotmatch '\\.venv' )} | Remove-Item -Force
-Write-Host "OK" -ForegroundColor green
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Generating zip from current sources ..."
+Write-Color -Text ">>> ", "Generating zip from current sources ..." -Color Green, Gray
$env:PYTHONPATH="$($openpype_root);$($env:PYTHONPATH)"
$env:OPENPYPE_ROOT="$($openpype_root)"
& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\tools\create_zip.py" $ARGS
diff --git a/tools/fetch_thirdparty_libs.ps1 b/tools/fetch_thirdparty_libs.ps1
index 16f7b70e7a..4df007ad67 100644
--- a/tools/fetch_thirdparty_libs.ps1
+++ b/tools/fetch_thirdparty_libs.ps1
@@ -15,6 +15,9 @@ $current_dir = Get-Location
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$openpype_root = (Get-Item $script_dir).parent.FullName
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell"
+
$env:_INSIDE_OPENPYPE_TOOL = "1"
if (-not (Test-Path 'env:POETRY_HOME')) {
@@ -23,16 +26,19 @@ if (-not (Test-Path 'env:POETRY_HOME')) {
Set-Location -Path $openpype_root
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
- Write-Host "*** " -NoNewline -ForegroundColor Yellow
- Write-Host "We need to install Poetry create virtual env first ..."
+ Write-Color -Text "NOT FOUND" -Color Yellow
+ Write-Color -Text "*** ", "We need to install Poetry create virtual env first ..." -Color Yellow, Gray
& "$openpype_root\tools\create_env.ps1"
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
-
+$startTime = [int][double]::Parse((Get-Date -UFormat %s))
& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\tools\fetch_thirdparty_libs.py"
+$endTime = [int][double]::Parse((Get-Date -UFormat %s))
Set-Location -Path $current_dir
+try
+{
+ New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype", "Dependencies downloaded", "All done in $( $endTime - $startTime ) secs."
+} catch {}
\ No newline at end of file
diff --git a/tools/make_docs.ps1 b/tools/make_docs.ps1
index 45a11171ae..43ecd0c09c 100644
--- a/tools/make_docs.ps1
+++ b/tools/make_docs.ps1
@@ -44,27 +44,30 @@ $art = @"
"@
+$current_dir = Get-Location
+$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$openpype_root = (Get-Item $script_dir).parent.FullName
+
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell"
+
Write-Host $art -ForegroundColor DarkGreen
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
- Write-Host "*** " -NoNewline -ForegroundColor Yellow
- Write-Host "We need to install Poetry create virtual env first ..."
- & "$openpype_root\tools\create_env.ps1"
+ Write-Color -Text "NOT FOUND" -Color Yellow
+ Install-Poetry
+ Write-Color -Text "INSTALLED" -Color Cyan
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
-Write-Host "This will not overwrite existing source rst files, only scan and add new."
+Write-Color -Text "... ", "This will not overwrite existing source rst files, only scan and add new." -Color Yellow, Gray
Set-Location -Path $openpype_root
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Running apidoc ..."
+Write-Color -Text ">>> ", "Running apidoc ..." -Color Green, Gray
& "$env:POETRY_HOME\bin\poetry" run sphinx-apidoc -M -e -d 10 --ext-intersphinx --ext-todo --ext-coverage --ext-viewcode -o "$($openpype_root)\docs\source" igniter
& "$env:POETRY_HOME\bin\poetry" run sphinx-apidoc.exe -M -e -d 10 --ext-intersphinx --ext-todo --ext-coverage --ext-viewcode -o "$($openpype_root)\docs\source" openpype vendor, openpype\vendor
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Building html ..."
+Write-Color -Text ">>> ", "Building html ..." -Color Green, Gray
& "$env:POETRY_HOME\bin\poetry" run python "$($openpype_root)\setup.py" build_sphinx
Set-Location -Path $current_dir
diff --git a/tools/modules/powershell/BurntToast b/tools/modules/powershell/BurntToast
new file mode 160000
index 0000000000..f58c9a26d6
--- /dev/null
+++ b/tools/modules/powershell/BurntToast
@@ -0,0 +1 @@
+Subproject commit f58c9a26d6ede30ecc7998e92b26974887e945fe
diff --git a/tools/modules/powershell/PSWriteColor b/tools/modules/powershell/PSWriteColor
new file mode 160000
index 0000000000..12eda384eb
--- /dev/null
+++ b/tools/modules/powershell/PSWriteColor
@@ -0,0 +1 @@
+Subproject commit 12eda384ebd7a7954e15855e312215c009c97114
diff --git a/tools/pack_project.ps1 b/tools/pack_project.ps1
new file mode 100644
index 0000000000..856247f7ca
--- /dev/null
+++ b/tools/pack_project.ps1
@@ -0,0 +1,39 @@
+<#
+.SYNOPSIS
+ Helper script OpenPype Packing project.
+
+.DESCRIPTION
+ Once you are happy with the project and want to preserve it for future work, just change the project name on line 38 and copy the file into .\OpenPype\tools. Then use the cmd form .EXAMPLE
+
+.EXAMPLE
+
+PS> .\tools\run_pack_project.ps1
+
+#>
+$current_dir = Get-Location
+$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$openpype_root = (Get-Item $script_dir).parent.FullName
+
+$env:_INSIDE_OPENPYPE_TOOL = "1"
+
+# make sure Poetry is in PATH
+if (-not (Test-Path 'env:POETRY_HOME')) {
+ $env:POETRY_HOME = "$openpype_root\.poetry"
+}
+$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
+
+Set-Location -Path $openpype_root
+
+Write-Host ">>> " -NoNewline -ForegroundColor Green
+Write-Host "Reading Poetry ... " -NoNewline
+if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
+ Write-Host "NOT FOUND" -ForegroundColor Yellow
+ Write-Host "*** " -NoNewline -ForegroundColor Yellow
+ Write-Host "We need to install Poetry create virtual env first ..."
+ & "$openpype_root\tools\create_env.ps1"
+} else {
+ Write-Host "OK" -ForegroundColor Green
+}
+
+& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" pack-project --project $ARGS
+Set-Location -Path $current_dir
\ No newline at end of file
diff --git a/tools/run_mongo.ps1 b/tools/run_mongo.ps1
index f6fa37207d..c64ff75969 100644
--- a/tools/run_mongo.ps1
+++ b/tools/run_mongo.ps1
@@ -11,6 +11,13 @@ PS> .\run_mongo.ps1
#>
+$current_dir = Get-Location
+$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$openpype_root = (Get-Item $script_dir).parent.FullName
+
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell"
+
$art = @"
. . .. . ..
@@ -43,8 +50,7 @@ function Exit-WithCode($exitcode) {
function Find-Mongo ($preferred_version) {
$defaultPath = "C:\Program Files\MongoDB\Server"
- Write-Host ">>> " -NoNewLine -ForegroundColor Green
- Write-Host "Detecting MongoDB ... " -NoNewline
+ Write-Color -Text ">>> ", "Detecting MongoDB ... " -Color Green, Gray -NoNewline
if (-not (Get-Command "mongod" -ErrorAction SilentlyContinue)) {
if(Test-Path "$($defaultPath)\*\bin\mongod.exe" -PathType Leaf) {
# we have mongo server installed on standard Windows location
@@ -52,17 +58,14 @@ function Find-Mongo ($preferred_version) {
# $preferred_version.
$mongoVersions = Get-ChildItem -Directory 'C:\Program Files\MongoDB\Server' | Sort-Object -Property {$_.Name -as [int]}
if(Test-Path "$($mongoVersions[-1])\bin\mongod.exe" -PathType Leaf) {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
$use_version = $mongoVersions[-1]
foreach ($v in $mongoVersions) {
- Write-Host " - found [ " -NoNewline
- Write-Host $v -NoNewLine -ForegroundColor Cyan
- Write-Host " ]" -NoNewLine
-
+ Write-Color -Text " - found [ ", $v, " ]" -Color Cyan, White, Cyan -NoNewLine
$version = Split-Path $v -Leaf
if ($preferred_version -eq $version) {
- Write-Host " *" -ForegroundColor Green
+ Write-Color -Text " *" -Color Green
$use_version = $v
} else {
Write-Host ""
@@ -71,27 +74,20 @@ function Find-Mongo ($preferred_version) {
$env:PATH = "$($env:PATH);$($use_version)\bin\"
- Write-Host " - auto-added from [ " -NoNewline
- Write-Host "$($use_version)\bin\mongod.exe" -NoNewLine -ForegroundColor Cyan
- Write-Host " ]"
+ Write-Color -Text " - auto-added from [ ", "$($use_version)\bin\mongod.exe", " ]" -Color Cyan, White, Cyan
return "$($use_version)\bin\mongod.exe"
} else {
- Write-Host "FAILED " -NoNewLine -ForegroundColor Red
- Write-Host "MongoDB not detected" -ForegroundColor Yellow
- Write-Host "Tried to find it on standard location " -NoNewline -ForegroundColor Gray
- Write-Host " [ " -NoNewline -ForegroundColor Cyan
- Write-Host "$($mongoVersions[-1])\bin\mongod.exe" -NoNewline -ForegroundColor White
- Write-Host " ] " -NoNewLine -ForegroundColor Cyan
- Write-Host "but failed." -ForegroundColor Gray
+ Write-Color -Text "FAILED " -Color Red -NoNewLine
+ Write-Color -Text "MongoDB not detected" -Color Yellow
+ Write-Color -Text "Tried to find it on standard location ", "[ ", "$($mongoVersions[-1])\bin\mongod.exe", " ]", " but failed." -Color Gray, Cyan, White, Cyan, Gray -NoNewline
Exit-WithCode 1
}
} else {
- Write-Host "FAILED " -NoNewLine -ForegroundColor Red
- Write-Host "MongoDB not detected in PATH" -ForegroundColor Yellow
+ Write-Color -Text "FAILED ", "MongoDB not detected in PATH" -Color Red, Yellow
Exit-WithCode 1
}
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
return Get-Command "mongod" -ErrorAction SilentlyContinue
}
<#
@@ -104,9 +100,6 @@ function Find-Mongo ($preferred_version) {
#>
}
-$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
-$openpype_root = (Get-Item $script_dir).parent.FullName
-
# mongodb port
$port = 2707
@@ -116,15 +109,7 @@ $dbpath = (Get-Item $openpype_root).parent.FullName + "\mongo_db_data"
$preferred_version = "5.0"
$mongoPath = Find-Mongo $preferred_version
-Write-Host ">>> " -NoNewLine -ForegroundColor Green
-Write-Host "Using DB path: " -NoNewLine
-Write-Host " [ " -NoNewline -ForegroundColor Cyan
-Write-Host "$($dbpath)" -NoNewline -ForegroundColor White
-Write-Host " ] "-ForegroundColor Cyan
-Write-Host ">>> " -NoNewLine -ForegroundColor Green
-Write-Host "Port: " -NoNewLine
-Write-Host " [ " -NoNewline -ForegroundColor Cyan
-Write-Host "$($port)" -NoNewline -ForegroundColor White
-Write-Host " ] " -ForegroundColor Cyan
-Start-Process -FilePath $mongopath "--dbpath $($dbpath) --port $($port)" -PassThru | Out-Null
+Write-Color -Text ">>> ", "Using DB path: ", "[ ", "$($dbpath)", " ]" -Color Green, Gray, Cyan, White, Cyan
+Write-Color -Text ">>> ", "Port: ", "[ ", "$($port)", " ]" -Color Green, Gray, Cyan, White, Cyan
+Start-Process -FilePath $mongopath "--dbpath $($dbpath) --port $($port)" -PassThru | Out-Null
diff --git a/tools/run_project_manager.ps1 b/tools/run_project_manager.ps1
index a9cfbb1e7b..c1813e4ed9 100644
--- a/tools/run_project_manager.ps1
+++ b/tools/run_project_manager.ps1
@@ -35,6 +35,9 @@ $current_dir = Get-Location
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$openpype_root = (Get-Item $script_dir).parent.FullName
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell"
+
$env:_INSIDE_OPENPYPE_TOOL = "1"
# make sure Poetry is in PATH
@@ -45,15 +48,13 @@ $env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
Set-Location -Path $openpype_root
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
- Write-Host "*** " -NoNewline -ForegroundColor Yellow
- Write-Host "We need to install Poetry create virtual env first ..."
- & "$openpype_root\tools\create_env.ps1"
+ Write-Color -Text "NOT FOUND" -Color Yellow
+ Install-Poetry
+ Write-Color -Text "INSTALLED" -Color Cyan
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
& "$env:POETRY_HOME\bin\poetry" run python "$($openpype_root)\start.py" projectmanager
diff --git a/tools/run_settings.ps1 b/tools/run_settings.ps1
index 1c0aa6e8f3..c74ae1ea3a 100644
--- a/tools/run_settings.ps1
+++ b/tools/run_settings.ps1
@@ -15,6 +15,9 @@ $current_dir = Get-Location
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$openpype_root = (Get-Item $script_dir).parent.FullName
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell"
+
$env:_INSIDE_OPENPYPE_TOOL = "1"
# make sure Poetry is in PATH
@@ -25,15 +28,13 @@ $env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
Set-Location -Path $openpype_root
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
- Write-Host "*** " -NoNewline -ForegroundColor Yellow
- Write-Host "We need to install Poetry create virtual env first ..."
- & "$openpype_root\tools\create_env.ps1"
+ Write-Color -Text "NOT FOUND" -Color Yellow
+ Install-Poetry
+ Write-Color -Text "INSTALLED" -Color Cyan
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
& "$env:POETRY_HOME\bin\poetry" run python "$($openpype_root)\start.py" settings --dev
diff --git a/tools/run_tests.ps1 b/tools/run_tests.ps1
index e631cb72df..4fa598c413 100644
--- a/tools/run_tests.ps1
+++ b/tools/run_tests.ps1
@@ -11,6 +11,13 @@ PS> .\run_test.ps1
#>
+$current_dir = Get-Location
+$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$openpype_root = (Get-Item $script_dir).parent.FullName
+
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell"
+
function Exit-WithCode($exitcode) {
# Only exit this host process if it's a child of another PowerShell parent process...
$parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId
@@ -22,10 +29,8 @@ function Exit-WithCode($exitcode) {
function Show-PSWarning() {
if ($PSVersionTable.PSVersion.Major -lt 7) {
- Write-Host "!!! " -NoNewline -ForegroundColor Red
- Write-Host "You are using old version of PowerShell. $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)"
- Write-Host "Please update to at least 7.0 - " -NoNewline -ForegroundColor Gray
- Write-Host "https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White
+ Write-Color -Text "!!! ", "You are using old version of PowerShell - ", "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" -Color Red, Yellow, White
+ Write-Color -Text " Please update to at least 7.0 - ", "https://github.com/PowerShell/PowerShell/releases" -Color Yellow, White
Exit-WithCode 1
}
}
@@ -53,10 +58,6 @@ Write-Host $art -ForegroundColor DarkGreen
# Enable if PS 7.x is needed.
# Show-PSWarning
-$current_dir = Get-Location
-$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
-$openpype_root = (Get-Item $script_dir).parent.FullName
-
$env:_INSIDE_OPENPYPE_TOOL = "1"
if (-not (Test-Path 'env:POETRY_HOME')) {
@@ -69,46 +70,32 @@ $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py"
$result = [regex]::Matches($version_file, '__version__ = "(?\d+\.\d+.\d+.*)"')
$openpype_version = $result[0].Groups['version'].Value
if (-not $openpype_version) {
- Write-Host "!!! " -ForegroundColor yellow -NoNewline
- Write-Host "Cannot determine OpenPype version."
+ Write-Color -Text "!!! ", "Cannot determine OpenPype version." -Color Yellow, Gray
Exit-WithCode 1
}
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "OpenPype [ " -NoNewline -ForegroundColor white
-Write-host $openpype_version -NoNewline -ForegroundColor green
-Write-Host " ] ..." -ForegroundColor white
+Write-Color -Text ">>> ", "OpenPype [ ", $openpype_version, " ]" -Color Green, White, Cyan, White
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
- Write-Host "*** " -NoNewline -ForegroundColor Yellow
- Write-Host "We need to install Poetry create virtual env first ..."
+ Write-Color -Text "NOT FOUND" -Color Yellow
+ Write-Color -Text "*** ", "We need to install Poetry create virtual env first ..." -Color Yellow, Gray
& "$openpype_root\tools\create_env.ps1"
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Cleaning cache files ... " -NoNewline
+Write-Color -Text ">>> ", "Cleaning cache files ... " -Color Green, Gray -NoNewline
Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force
+Get-ChildItem $openpype_root -Filter "*.pyo" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force
Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force -Recurse
-Write-Host "OK" -ForegroundColor green
+Write-Color -Text "OK" -Color green
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Testing OpenPype ..."
+Write-Color -Text ">>> ", "Testing OpenPype ..." -Color Green, White
$original_pythonpath = $env:PYTHONPATH
$env:PYTHONPATH="$($openpype_root);$($env:PYTHONPATH)"
& "$env:POETRY_HOME\bin\poetry" run pytest -x --capture=sys --print -W ignore::DeprecationWarning "$($openpype_root)/tests"
$env:PYTHONPATH = $original_pythonpath
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "restoring current directory"
+Write-Color -Text ">>> ", "Restoring current directory" -Color Green, Gray
Set-Location -Path $current_dir
-
-
-
-
-
-
diff --git a/tools/run_tray.ps1 b/tools/run_tray.ps1
index 872c1524a6..40157c4e81 100644
--- a/tools/run_tray.ps1
+++ b/tools/run_tray.ps1
@@ -14,6 +14,9 @@ $current_dir = Get-Location
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$openpype_root = (Get-Item $script_dir).parent.FullName
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\tools\modules\powershell"
+
$env:_INSIDE_OPENPYPE_TOOL = "1"
# make sure Poetry is in PATH
@@ -24,15 +27,13 @@ $env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
Set-Location -Path $openpype_root
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
- Write-Host "*** " -NoNewline -ForegroundColor Yellow
- Write-Host "We need to install Poetry create virtual env first ..."
+ Write-Color -Text "NOT FOUND" -Color Yellow
+ Write-Color -Text "*** ", "We need to install Poetry create virtual env first ..." -Color Yellow, Gray
& "$openpype_root\tools\create_env.ps1"
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" tray --debug
diff --git a/tools/unpack_project.ps1 b/tools/unpack_project.ps1
new file mode 100644
index 0000000000..e7b9e87a7f
--- /dev/null
+++ b/tools/unpack_project.ps1
@@ -0,0 +1,39 @@
+<#
+.SYNOPSIS
+ Helper script OpenPype Unpacking project.
+
+.DESCRIPTION
+ Make sure you had dropped the project from your db and removed the poject data in case you were having them previously. Then on line 38 change the to any path where the zip with project is - usually we are having it here https://drive.google.com/drive/u/0/folders/0AKE4mxImOsAGUk9PVA . Copy the file into .\OpenPype\tools. Then use the cmd form .EXAMPLE
+
+.EXAMPLE
+
+PS> .\tools\run_unpack_project.ps1
+
+#>
+$current_dir = Get-Location
+$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$openpype_root = (Get-Item $script_dir).parent.FullName
+
+$env:_INSIDE_OPENPYPE_TOOL = "1"
+
+# make sure Poetry is in PATH
+if (-not (Test-Path 'env:POETRY_HOME')) {
+ $env:POETRY_HOME = "$openpype_root\.poetry"
+}
+$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
+
+Set-Location -Path $openpype_root
+
+Write-Host ">>> " -NoNewline -ForegroundColor Green
+Write-Host "Reading Poetry ... " -NoNewline
+if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
+ Write-Host "NOT FOUND" -ForegroundColor Yellow
+ Write-Host "*** " -NoNewline -ForegroundColor Yellow
+ Write-Host "We need to install Poetry create virtual env first ..."
+ & "$openpype_root\tools\create_env.ps1"
+} else {
+ Write-Host "OK" -ForegroundColor Green
+}
+
+& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" unpack-project --zipfile $ARGS
+Set-Location -Path $current_dir
\ No newline at end of file
diff --git a/website/docs/admin_hosts_hiero.md b/website/docs/admin_hosts_hiero.md
new file mode 100644
index 0000000000..b75d8dee7d
--- /dev/null
+++ b/website/docs/admin_hosts_hiero.md
@@ -0,0 +1,9 @@
+---
+id: admin_hosts_hiero
+title: Hiero
+sidebar_label: Hiero
+---
+
+## Custom Menu
+You can add your custom tools menu into Hiero by extending definitions in **Hiero -> Scripts Menu Definition**.
+
diff --git a/website/docs/admin_openpype_commands.md b/website/docs/admin_openpype_commands.md
index 53b4799d6e..53fc12410f 100644
--- a/website/docs/admin_openpype_commands.md
+++ b/website/docs/admin_openpype_commands.md
@@ -45,6 +45,7 @@ For more information [see here](admin_use.md#run-openpype).
| publish | Pype takes JSON from provided path and use it to publish data in it. | [📑](#publish-arguments) |
| extractenvironments | Extract environment variables for entered context to a json file. | [📑](#extractenvironments-arguments) |
| run | Execute given python script within OpenPype environment. | [📑](#run-arguments) |
+| interactive | Start python like interactive console session. | |
| projectmanager | Launch Project Manager UI | [📑](#projectmanager-arguments) |
| settings | Open Settings UI | [📑](#settings-arguments) |
| standalonepublisher | Open Standalone Publisher UI | [📑](#standalonepublisher-arguments) |
diff --git a/website/docs/assets/hiero-admin_scriptsmenu.png b/website/docs/assets/hiero-admin_scriptsmenu.png
new file mode 100644
index 0000000000..6de136a434
Binary files /dev/null and b/website/docs/assets/hiero-admin_scriptsmenu.png differ
diff --git a/website/docs/dev_publishing.md b/website/docs/dev_publishing.md
index 8ee3b7e85f..f11a2c3047 100644
--- a/website/docs/dev_publishing.md
+++ b/website/docs/dev_publishing.md
@@ -66,7 +66,7 @@ Another optional function is **get_current_context**. This function is handy in
Main responsibility of create plugin is to create, update, collect and remove instance metadata and propagate changes to create context. Has access to **CreateContext** (`self.create_context`) that discovered the plugin so has also access to other creators and instances. Create plugins have a lot of responsibility so it is recommended to implement common code per host.
#### *BaseCreator*
-Base implementation of creator plugin. It is not recommended to use this class as base for production plugins but rather use one of **AutoCreator** and **Creator** variants.
+Base implementation of creator plugin. It is not recommended to use this class as base for production plugins but rather use one of **HiddenCreator**, **AutoCreator** and **Creator** variants.
**Abstractions**
- **`family`** (class attr) - Tells what kind of instance will be created.
@@ -92,7 +92,7 @@ def collect_instances(self):
self._add_instance_to_context(instance)
```
-- **`create`** (method) - Create a new object of **CreatedInstance** store its metadata to the workfile and add the instance into the created context. Failed Creating should raise **CreatorError** if an error happens that artists can fix or give them some useful information. Triggers and implementation differs for **Creator** and **AutoCreator**.
+- **`create`** (method) - Create a new object of **CreatedInstance** store its metadata to the workfile and add the instance into the created context. Failed Creating should raise **CreatorError** if an error happens that artists can fix or give them some useful information. Triggers and implementation differs for **Creator**, **HiddenCreator** and **AutoCreator**.
- **`update_instances`** (method) - Update data of instances. Receives tuple with **instance** and **changes**.
```python
@@ -172,11 +172,11 @@ class RenderLayerCreator(Creator):
icon = "fa5.building"
```
-- **`get_instance_attr_defs`** (method) - Attribute definitions of instance. Creator can define attribute values with default values for each instance. These attributes may affect how instances will be instance processed during publishing. Attribute defiitions can be used from `openpype.pipeline.lib.attribute_definitions` (NOTE: Will be moved to `openpype.lib.attribute_definitions` soon). Attribute definitions define basic types of values for different cases e.g. boolean, number, string, enumerator, etc. Default implementation returns **instance_attr_defs**.
+- **`get_instance_attr_defs`** (method) - Attribute definitions of instance. Creator can define attribute values with default values for each instance. These attributes may affect how instances will be instance processed during publishing. Attribute defiitions can be used from `openpype.lib.attribute_definitions`. Attribute definitions define basic types of values for different cases e.g. boolean, number, string, enumerator, etc. Default implementation returns **instance_attr_defs**.
- **`instance_attr_defs`** (attr) - Attribute for default implementation of **get_instance_attr_defs**.
```python
-from openpype.pipeline import attribute_definitions
+from openpype.lib import attribute_definitions
class RenderLayerCreator(Creator):
@@ -199,6 +199,20 @@ class RenderLayerCreator(Creator):
- **`get_dynamic_data`** (method) - Can be used to extend data for subset templates which may be required in some cases.
+#### *HiddenCreator*
+Creator which is not showed in UI so artist can't trigger it directly but is available for other creators. This creator is primarily meant for cases when creation should create different types of instances. For example during editorial publishing where input is single edl file but should create 2 or more kind of instances each with different family, attributes and abilities. Arguments for creation were limited to `instance_data` and `source_data`. Data of `instance_data` should follow what is sent to other creators and `source_data` can be used to send custom data defined by main creator. It is expected that `HiddenCreator` has specific main or "parent" creator.
+
+```python
+def create(self, instance_data, source_data):
+ variant = instance_data["variant"]
+ task_name = instance_data["task"]
+ asset_name = instance_data["asset"]
+ asset_doc = get_asset_by_name(self.project_name, asset_name)
+ self.get_subset_name(
+ variant, task_name, asset_doc, self.project_name, self.host_name)
+```
+
+
#### *AutoCreator*
Creator that is triggered on reset of create context. Can be used for families that are expected to be created automatically without artist interaction (e.g. **workfile**). Method `create` is triggered after collecting all creators.
@@ -234,14 +248,14 @@ def create(self):
# - variant can be filled from settings
variant = self._variant_name
# Only place where we can look for current context
- project_name = io.Session["AVALON_PROJECT"]
- asset_name = io.Session["AVALON_ASSET"]
- task_name = io.Session["AVALON_TASK"]
- host_name = io.Session["AVALON_APP"]
+ project_name = self.project_name
+ asset_name = legacy_io.Session["AVALON_ASSET"]
+ task_name = legacy_io.Session["AVALON_TASK"]
+ host_name = legacy_io.Session["AVALON_APP"]
# Create new instance if does not exist yet
if existing_instance is None:
- asset_doc = io.find_one({"type": "asset", "name": asset_name})
+ asset_doc = get_asset_by_name(project_name, asset_name)
subset_name = self.get_subset_name(
variant, task_name, asset_doc, project_name, host_name
)
@@ -264,7 +278,7 @@ def create(self):
existing_instance["asset"] != asset_name
or existing_instance["task"] != task_name
):
- asset_doc = io.find_one({"type": "asset", "name": asset_name})
+ asset_doc = get_asset_by_name(project_name, asset_name)
subset_name = self.get_subset_name(
variant, task_name, asset_doc, project_name, host_name
)
@@ -297,7 +311,8 @@ class BulkRenderCreator(Creator):
- **`pre_create_attr_defs`** (attr) - Attribute for default implementation of **get_pre_create_attr_defs**.
```python
-from openpype.pipeline import Creator, attribute_definitions
+from openpype.lib import attribute_definitions
+from openpype.pipeline.create import Creator
class CreateRender(Creator):
@@ -470,10 +485,8 @@ Possible attribute definitions can be found in `openpype/pipeline/lib/attribute_
```python
import pyblish.api
-from openpype.pipeline import (
- OpenPypePyblishPluginMixin,
- attribute_definitions,
-)
+from openpype.lib import attribute_definitions
+from openpype.pipeline import OpenPypePyblishPluginMixin
# Example context plugin
diff --git a/website/sidebars.js b/website/sidebars.js
index 0e578bd085..9d60a5811c 100644
--- a/website/sidebars.js
+++ b/website/sidebars.js
@@ -100,6 +100,7 @@ module.exports = {
label: "Integrations",
items: [
"admin_hosts_blender",
+ "admin_hosts_hiero",
"admin_hosts_maya",
"admin_hosts_nuke",
"admin_hosts_resolve",