.*)_(.*)_SHD", title="Validation regex")
+
+
+class ValidateAttributesModel(BaseSettingsModel):
+ enabled: bool = Field(title="ValidateAttributes")
+ attributes: str = Field(
+ "{}", title="Attributes", widget="textarea")
+
+ @validator("attributes")
+ def validate_json(cls, value):
+ if not value.strip():
+ return "{}"
+ try:
+ converted_value = json.loads(value)
+ success = isinstance(converted_value, dict)
+ except json.JSONDecodeError:
+ success = False
+
+ if not success:
+ raise BadRequestException(
+ "The attibutes can't be parsed as json object"
+ )
+ return value
+
+
+class ValidateLoadedPluginModel(BaseSettingsModel):
+ enabled: bool = Field(title="ValidateLoadedPlugin")
+ optional: bool = Field(title="Optional")
+ whitelist_native_plugins: bool = Field(
+ title="Whitelist Maya Native Plugins"
+ )
+ authorized_plugins: list[str] = Field(
+ default_factory=list, title="Authorized plugins"
+ )
+
+
+class ValidateMayaUnitsModel(BaseSettingsModel):
+ enabled: bool = Field(title="ValidateMayaUnits")
+ optional: bool = Field(title="Optional")
+ validate_linear_units: bool = Field(title="Validate linear units")
+ linear_units: str = Field(
+ enum_resolver=linear_unit_enum, title="Linear Units"
+ )
+ validate_angular_units: bool = Field(title="Validate angular units")
+ angular_units: str = Field(
+ enum_resolver=angular_unit_enum, title="Angular units"
+ )
+ validate_fps: bool = Field(title="Validate fps")
+
+
+class ValidateUnrealStaticMeshNameModel(BaseSettingsModel):
+ enabled: bool = Field(title="ValidateUnrealStaticMeshName")
+ optional: bool = Field(title="Optional")
+ validate_mesh: bool = Field(title="Validate mesh names")
+ validate_collision: bool = Field(title="Validate collison names")
+
+
+class ValidateCycleErrorModel(BaseSettingsModel):
+ enabled: bool = Field(title="ValidateCycleError")
+ optional: bool = Field(title="Optional")
+ families: list[str] = Field(default_factory=list, title="Families")
+
+
+class ValidatePluginPathAttributesAttrModel(BaseSettingsModel):
+ name: str = Field(title="Node type")
+ value: str = Field(title="Attribute")
+
+
+class ValidatePluginPathAttributesModel(BaseSettingsModel):
+ """Fill in the node types and attributes you want to validate.
+
+ e.g. AlembicNode.abc_file, the node type is AlembicNode
+ and the node attribute is abc_file
+ """
+
+ enabled: bool = True
+ optional: bool = Field(title="Optional")
+ active: bool = Field(title="Active")
+ attribute: list[ValidatePluginPathAttributesAttrModel] = Field(
+ default_factory=list,
+ title="File Attribute"
+ )
+
+ @validator("attribute")
+ def validate_unique_outputs(cls, value):
+ ensure_unique_names(value)
+ return value
+
+
+# Validate Render Setting
+class RendererAttributesModel(BaseSettingsModel):
+ _layout = "compact"
+ type: str = Field(title="Type")
+ value: str = Field(title="Value")
+
+
+class ValidateRenderSettingsModel(BaseSettingsModel):
+ arnold_render_attributes: list[RendererAttributesModel] = Field(
+ default_factory=list, title="Arnold Render Attributes")
+ vray_render_attributes: list[RendererAttributesModel] = Field(
+ default_factory=list, title="VRay Render Attributes")
+ redshift_render_attributes: list[RendererAttributesModel] = Field(
+ default_factory=list, title="Redshift Render Attributes")
+ renderman_render_attributes: list[RendererAttributesModel] = Field(
+ default_factory=list, title="Renderman Render Attributes")
+
+
+class BasicValidateModel(BaseSettingsModel):
+ enabled: bool = Field(title="Enabled")
+ optional: bool = Field(title="Optional")
+ active: bool = Field(title="Active")
+
+
+class ValidateCameraContentsModel(BaseSettingsModel):
+ enabled: bool = Field(title="Enabled")
+ optional: bool = Field(title="Optional")
+ validate_shapes: bool = Field(title="Validate presence of shapes")
+
+
+class ExtractProxyAlembicModel(BaseSettingsModel):
+ enabled: bool = Field(title="Enabled")
+ families: list[str] = Field(
+ default_factory=list,
+ title="Families")
+
+
+class ExtractAlembicModel(BaseSettingsModel):
+ enabled: bool = Field(title="Enabled")
+ families: list[str] = Field(
+ default_factory=list,
+ title="Families")
+
+
+class ExtractObjModel(BaseSettingsModel):
+ enabled: bool = Field(title="Enabled")
+ optional: bool = Field(title="Optional")
+
+
+class ExtractMayaSceneRawModel(BaseSettingsModel):
+ """Add loaded instances to those published families:"""
+ enabled: bool = Field(title="ExtractMayaSceneRaw")
+ add_for_families: list[str] = Field(default_factory=list, title="Families")
+
+
+class ExtractCameraAlembicModel(BaseSettingsModel):
+ """
+ List of attributes that will be added to the baked alembic camera. Needs to be written in python list syntax.
+ """
+ enabled: bool = Field(title="ExtractCameraAlembic")
+ optional: bool = Field(title="Optional")
+ active: bool = Field(title="Active")
+ bake_attributes: str = Field(
+ "[]", title="Base Attributes", widget="textarea"
+ )
+
+ @validator("bake_attributes")
+ def validate_json_list(cls, value):
+ if not value.strip():
+ return "[]"
+ try:
+ converted_value = json.loads(value)
+ success = isinstance(converted_value, list)
+ except json.JSONDecodeError:
+ success = False
+
+ if not success:
+ raise BadRequestException(
+ "The text can't be parsed as json object"
+ )
+ return value
+
+
+class ExtractGLBModel(BaseSettingsModel):
+ enabled: bool = True
+ active: bool = Field(title="Active")
+ ogsfx_path: str = Field(title="GLSL Shader Directory")
+
+
+class ExtractLookArgsModel(BaseSettingsModel):
+ argument: str = Field(title="Argument")
+ parameters: list[str] = Field(default_factory=list, title="Parameters")
+
+
+class ExtractLookModel(BaseSettingsModel):
+ maketx_arguments: list[ExtractLookArgsModel] = Field(
+ default_factory=list,
+ title="Extra arguments for maketx command line"
+ )
+
+
+class ExtractGPUCacheModel(BaseSettingsModel):
+ enabled: bool = True
+ families: list[str] = Field(default_factory=list, title="Families")
+ step: float = Field(1.0, ge=1.0, title="Step")
+ stepSave: int = Field(1, ge=1, title="Step Save")
+ optimize: bool = Field(title="Optimize Hierarchy")
+ optimizationThreshold: int = Field(1, ge=1, title="Optimization Threshold")
+ optimizeAnimationsForMotionBlur: bool = Field(
+ title="Optimize Animations For Motion Blur"
+ )
+ writeMaterials: bool = Field(title="Write Materials")
+ useBaseTessellation: bool = Field(title="User Base Tesselation")
+
+
+class PublishersModel(BaseSettingsModel):
+ CollectMayaRender: CollectMayaRenderModel = Field(
+ default_factory=CollectMayaRenderModel,
+ title="Collect Render Layers",
+ section="Collectors"
+ )
+ CollectFbxCamera: CollectFbxCameraModel = Field(
+ default_factory=CollectFbxCameraModel,
+ title="Collect Camera for FBX export",
+ )
+ CollectGLTF: CollectGLTFModel = Field(
+ default_factory=CollectGLTFModel,
+ title="Collect Assets for GLB/GLTF export"
+ )
+ ValidateInstanceInContext: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Instance In Context",
+ section="Validators"
+ )
+ ValidateContainers: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Containers"
+ )
+ ValidateFrameRange: ValidateFrameRangeModel = Field(
+ default_factory=ValidateFrameRangeModel,
+ title="Validate Frame Range"
+ )
+ ValidateShaderName: ValidateShaderNameModel = Field(
+ default_factory=ValidateShaderNameModel,
+ title="Validate Shader Name"
+ )
+ ValidateShadingEngine: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Look Shading Engine Naming"
+ )
+ ValidateMayaColorSpace: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Colorspace"
+ )
+ ValidateAttributes: ValidateAttributesModel = Field(
+ default_factory=ValidateAttributesModel,
+ title="Validate Attributes"
+ )
+ ValidateLoadedPlugin: ValidateLoadedPluginModel = Field(
+ default_factory=ValidateLoadedPluginModel,
+ title="Validate Loaded Plugin"
+ )
+ ValidateMayaUnits: ValidateMayaUnitsModel = Field(
+ default_factory=ValidateMayaUnitsModel,
+ title="Validate Maya Units"
+ )
+ ValidateUnrealStaticMeshName: ValidateUnrealStaticMeshNameModel = Field(
+ default_factory=ValidateUnrealStaticMeshNameModel,
+ title="Validate Unreal Static Mesh Name"
+ )
+ ValidateCycleError: ValidateCycleErrorModel = Field(
+ default_factory=ValidateCycleErrorModel,
+ title="Validate Cycle Error"
+ )
+ ValidatePluginPathAttributes: ValidatePluginPathAttributesModel = Field(
+ default_factory=ValidatePluginPathAttributesModel,
+ title="Plug-in Path Attributes"
+ )
+ ValidateRenderSettings: ValidateRenderSettingsModel = Field(
+ default_factory=ValidateRenderSettingsModel,
+ title="Validate Render Settings"
+ )
+ ValidateCurrentRenderLayerIsRenderable: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Current Render Layer Has Renderable Camera"
+ )
+ ValidateGLSLMaterial: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate GLSL Material"
+ )
+ ValidateGLSLPlugin: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate GLSL Plugin"
+ )
+ ValidateRenderImageRule: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Render Image Rule (Workspace)"
+ )
+ ValidateRenderNoDefaultCameras: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate No Default Cameras Renderable"
+ )
+ ValidateRenderSingleCamera: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Render Single Camera "
+ )
+ ValidateRenderLayerAOVs: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Render Passes/AOVs Are Registered"
+ )
+ ValidateStepSize: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Step Size"
+ )
+ ValidateVRayDistributedRendering: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="VRay Distributed Rendering"
+ )
+ ValidateVrayReferencedAOVs: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="VRay Referenced AOVs"
+ )
+ ValidateVRayTranslatorEnabled: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="VRay Translator Settings"
+ )
+ ValidateVrayProxy: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="VRay Proxy Settings"
+ )
+ ValidateVrayProxyMembers: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="VRay Proxy Members"
+ )
+ ValidateYetiRenderScriptCallbacks: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Yeti Render Script Callbacks"
+ )
+ ValidateYetiRigCacheState: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Yeti Rig Cache State"
+ )
+ ValidateYetiRigInputShapesInInstance: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Yeti Rig Input Shapes In Instance"
+ )
+ ValidateYetiRigSettings: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Yeti Rig Settings"
+ )
+ # Model - START
+ ValidateModelName: ValidateModelNameModel = Field(
+ default_factory=ValidateModelNameModel,
+ title="Validate Model Name",
+ section="Model",
+ )
+ ValidateModelContent: ValidateModelContentModel = Field(
+ default_factory=ValidateModelContentModel,
+ title="Validate Model Content",
+ )
+ ValidateTransformNamingSuffix: ValidateTransformNamingSuffixModel = Field(
+ default_factory=ValidateTransformNamingSuffixModel,
+ title="Validate Transform Naming Suffix",
+ )
+ ValidateColorSets: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Color Sets",
+ )
+ ValidateMeshHasOverlappingUVs: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Mesh Has Overlapping UVs",
+ )
+ ValidateMeshArnoldAttributes: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Mesh Arnold Attributes",
+ )
+ ValidateMeshShaderConnections: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Mesh Shader Connections",
+ )
+ ValidateMeshSingleUVSet: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Mesh Single UV Set",
+ )
+ ValidateMeshHasUVs: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Mesh Has UVs",
+ )
+ ValidateMeshLaminaFaces: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Mesh Lamina Faces",
+ )
+ ValidateMeshNgons: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Mesh Ngons",
+ )
+ ValidateMeshNonManifold: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Mesh Non-Manifold",
+ )
+ ValidateMeshNoNegativeScale: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Mesh No Negative Scale",
+ )
+ ValidateMeshNonZeroEdgeLength: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Mesh Edge Length Non Zero",
+ )
+ ValidateMeshNormalsUnlocked: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Mesh Normals Unlocked",
+ )
+ ValidateMeshUVSetMap1: ValidateMeshUVSetMap1Model = Field(
+ default_factory=ValidateMeshUVSetMap1Model,
+ title="Validate Mesh UV Set Map 1",
+ )
+ ValidateMeshVerticesHaveEdges: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Mesh Vertices Have Edges",
+ )
+ ValidateNoAnimation: ValidateNoAnimationModel = Field(
+ default_factory=ValidateNoAnimationModel,
+ title="Validate No Animation",
+ )
+ ValidateNoNamespace: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate No Namespace",
+ )
+ ValidateNoNullTransforms: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate No Null Transforms",
+ )
+ ValidateNoUnknownNodes: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate No Unknown Nodes",
+ )
+ ValidateNodeNoGhosting: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Node No Ghosting",
+ )
+ ValidateShapeDefaultNames: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Shape Default Names",
+ )
+ ValidateShapeRenderStats: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Shape Render Stats",
+ )
+ ValidateShapeZero: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Shape Zero",
+ )
+ ValidateTransformZero: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Transform Zero",
+ )
+ ValidateUniqueNames: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Unique Names",
+ )
+ ValidateNoVRayMesh: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate No V-Ray Proxies (VRayMesh)",
+ )
+ ValidateUnrealMeshTriangulated: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate if Mesh is Triangulated",
+ )
+ ValidateAlembicVisibleOnly: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Alembic Visible Node",
+ )
+ ExtractProxyAlembic: ExtractProxyAlembicModel = Field(
+ default_factory=ExtractProxyAlembicModel,
+ title="Extract Proxy Alembic",
+ section="Model Extractors",
+ )
+ ExtractAlembic: ExtractAlembicModel = Field(
+ default_factory=ExtractAlembicModel,
+ title="Extract Alembic",
+ )
+ ExtractObj: ExtractObjModel = Field(
+ default_factory=ExtractObjModel,
+ title="Extract OBJ"
+ )
+ # Model - END
+
+ # Rig - START
+ ValidateRigContents: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Rig Contents",
+ section="Rig",
+ )
+ ValidateRigJointsHidden: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Rig Joints Hidden",
+ )
+ ValidateRigControllers: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Rig Controllers",
+ )
+ ValidateAnimationContent: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Animation Content",
+ )
+ ValidateOutRelatedNodeIds: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Animation Out Set Related Node Ids",
+ )
+ ValidateRigControllersArnoldAttributes: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Rig Controllers (Arnold Attributes)",
+ )
+ ValidateSkeletalMeshHierarchy: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Skeletal Mesh Top Node",
+ )
+ ValidateSkinclusterDeformerSet: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Skincluster Deformer Relationships",
+ )
+ ValidateRigOutSetNodeIds: ValidateRigOutSetNodeIdsModel = Field(
+ default_factory=ValidateRigOutSetNodeIdsModel,
+ title="Validate Rig Out Set Node Ids",
+ )
+ # Rig - END
+ ValidateCameraAttributes: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Camera Attributes"
+ )
+ ValidateAssemblyName: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Assembly Name"
+ )
+ ValidateAssemblyNamespaces: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Assembly Namespaces"
+ )
+ ValidateAssemblyModelTransforms: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Assembly Model Transforms"
+ )
+ ValidateAssRelativePaths: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Ass Relative Paths"
+ )
+ ValidateInstancerContent: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Instancer Content"
+ )
+ ValidateInstancerFrameRanges: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Instancer Cache Frame Ranges"
+ )
+ ValidateNoDefaultCameras: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate No Default Cameras"
+ )
+ ValidateUnrealUpAxis: BasicValidateModel = Field(
+ default_factory=BasicValidateModel,
+ title="Validate Unreal Up-Axis Check"
+ )
+ ValidateCameraContents: ValidateCameraContentsModel = Field(
+ default_factory=ValidateCameraContentsModel,
+ title="Validate Camera Content"
+ )
+ ExtractPlayblast: ExtractPlayblastSetting = Field(
+ default_factory=ExtractPlayblastSetting,
+ title="Extract Playblast Settings",
+ section="Extractors"
+ )
+ ExtractMayaSceneRaw: ExtractMayaSceneRawModel = Field(
+ default_factory=ExtractMayaSceneRawModel,
+ title="Maya Scene(Raw)"
+ )
+ ExtractCameraAlembic: ExtractCameraAlembicModel = Field(
+ default_factory=ExtractCameraAlembicModel,
+ title="Extract Camera Alembic"
+ )
+ ExtractGLB: ExtractGLBModel = Field(
+ default_factory=ExtractGLBModel,
+ title="Extract GLB"
+ )
+ ExtractLook: ExtractLookModel = Field(
+ default_factory=ExtractLookModel,
+ title="Extract Look"
+ )
+ ExtractGPUCache: ExtractGPUCacheModel = Field(
+ default_factory=ExtractGPUCacheModel,
+ title="Extract GPU Cache",
+ )
+
+
+DEFAULT_SUFFIX_NAMING = {
+ "mesh": ["_GEO", "_GES", "_GEP", "_OSD"],
+ "nurbsCurve": ["_CRV"],
+ "nurbsSurface": ["_NRB"],
+ "locator": ["_LOC"],
+ "group": ["_GRP"]
+}
+
+DEFAULT_PUBLISH_SETTINGS = {
+ "CollectMayaRender": {
+ "sync_workfile_version": False
+ },
+ "CollectFbxCamera": {
+ "enabled": False
+ },
+ "CollectGLTF": {
+ "enabled": False
+ },
+ "ValidateInstanceInContext": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateContainers": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateFrameRange": {
+ "enabled": True,
+ "optional": True,
+ "active": True,
+ "exclude_product_types": [
+ "model",
+ "rig",
+ "staticMesh"
+ ]
+ },
+ "ValidateShaderName": {
+ "enabled": False,
+ "optional": True,
+ "active": True,
+ "regex": "(?P.*)_(.*)_SHD"
+ },
+ "ValidateShadingEngine": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateMayaColorSpace": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateAttributes": {
+ "enabled": False,
+ "attributes": "{}"
+ },
+ "ValidateLoadedPlugin": {
+ "enabled": False,
+ "optional": True,
+ "whitelist_native_plugins": False,
+ "authorized_plugins": []
+ },
+ "ValidateMayaUnits": {
+ "enabled": True,
+ "optional": False,
+ "validate_linear_units": True,
+ "linear_units": "cm",
+ "validate_angular_units": True,
+ "angular_units": "deg",
+ "validate_fps": True
+ },
+ "ValidateUnrealStaticMeshName": {
+ "enabled": True,
+ "optional": True,
+ "validate_mesh": False,
+ "validate_collision": True
+ },
+ "ValidateCycleError": {
+ "enabled": True,
+ "optional": False,
+ "families": [
+ "rig"
+ ]
+ },
+ "ValidatePluginPathAttributes": {
+ "enabled": True,
+ "optional": False,
+ "active": True,
+ "attribute": [
+ {"name": "AlembicNode", "value": "abc_File"},
+ {"name": "VRayProxy", "value": "fileName"},
+ {"name": "RenderManArchive", "value": "filename"},
+ {"name": "pgYetiMaya", "value": "cacheFileName"},
+ {"name": "aiStandIn", "value": "dso"},
+ {"name": "RedshiftSprite", "value": "tex0"},
+ {"name": "RedshiftBokeh", "value": "dofBokehImage"},
+ {"name": "RedshiftCameraMap", "value": "tex0"},
+ {"name": "RedshiftEnvironment", "value": "tex2"},
+ {"name": "RedshiftDomeLight", "value": "tex1"},
+ {"name": "RedshiftIESLight", "value": "profile"},
+ {"name": "RedshiftLightGobo", "value": "tex0"},
+ {"name": "RedshiftNormalMap", "value": "tex0"},
+ {"name": "RedshiftProxyMesh", "value": "fileName"},
+ {"name": "RedshiftVolumeShape", "value": "fileName"},
+ {"name": "VRayTexGLSL", "value": "fileName"},
+ {"name": "VRayMtlGLSL", "value": "fileName"},
+ {"name": "VRayVRmatMtl", "value": "fileName"},
+ {"name": "VRayPtex", "value": "ptexFile"},
+ {"name": "VRayLightIESShape", "value": "iesFile"},
+ {"name": "VRayMesh", "value": "materialAssignmentsFile"},
+ {"name": "VRayMtlOSL", "value": "fileName"},
+ {"name": "VRayTexOSL", "value": "fileName"},
+ {"name": "VRayTexOCIO", "value": "ocioConfigFile"},
+ {"name": "VRaySettingsNode", "value": "pmap_autoSaveFile2"},
+ {"name": "VRayScannedMtl", "value": "file"},
+ {"name": "VRayScene", "value": "parameterOverrideFilePath"},
+ {"name": "VRayMtlMDL", "value": "filename"},
+ {"name": "VRaySimbiont", "value": "file"},
+ {"name": "dlOpenVDBShape", "value": "filename"},
+ {"name": "pgYetiMayaShape", "value": "liveABCFilename"},
+ {"name": "gpuCache", "value": "cacheFileName"},
+ ]
+ },
+ "ValidateRenderSettings": {
+ "arnold_render_attributes": [],
+ "vray_render_attributes": [],
+ "redshift_render_attributes": [],
+ "renderman_render_attributes": []
+ },
+ "ValidateCurrentRenderLayerIsRenderable": {
+ "enabled": True,
+ "optional": False,
+ "active": True
+ },
+ "ValidateGLSLMaterial": {
+ "enabled": True,
+ "optional": False,
+ "active": True
+ },
+ "ValidateGLSLPlugin": {
+ "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,
+ "material_file": {
+ "windows": "",
+ "darwin": "",
+ "linux": ""
+ },
+ "regex": "(.*)_(\\d)*_(?P.*)_(GEO)",
+ "top_level_regex": ".*_GRP"
+ },
+ "ValidateModelContent": {
+ "enabled": True,
+ "optional": False,
+ "validate_top_group": True
+ },
+ "ValidateTransformNamingSuffix": {
+ "enabled": True,
+ "optional": True,
+ "SUFFIX_NAMING_TABLE": json.dumps(DEFAULT_SUFFIX_NAMING, indent=4),
+ "ALLOW_IF_NOT_IN_SUFFIX_TABLE": True
+ },
+ "ValidateColorSets": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateMeshHasOverlappingUVs": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateMeshArnoldAttributes": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateMeshShaderConnections": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateMeshSingleUVSet": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateMeshHasUVs": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateMeshLaminaFaces": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateMeshNgons": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateMeshNonManifold": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateMeshNoNegativeScale": {
+ "enabled": True,
+ "optional": False,
+ "active": True
+ },
+ "ValidateMeshNonZeroEdgeLength": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateMeshNormalsUnlocked": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateMeshUVSetMap1": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateMeshVerticesHaveEdges": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateNoAnimation": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateNoNamespace": {
+ "enabled": True,
+ "optional": False,
+ "active": True
+ },
+ "ValidateNoNullTransforms": {
+ "enabled": True,
+ "optional": False,
+ "active": True
+ },
+ "ValidateNoUnknownNodes": {
+ "enabled": True,
+ "optional": False,
+ "active": True
+ },
+ "ValidateNodeNoGhosting": {
+ "enabled": False,
+ "optional": False,
+ "active": True
+ },
+ "ValidateShapeDefaultNames": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateShapeRenderStats": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateShapeZero": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateTransformZero": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateUniqueNames": {
+ "enabled": False,
+ "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
+ },
+ "ExtractProxyAlembic": {
+ "enabled": True,
+ "families": [
+ "proxyAbc"
+ ]
+ },
+ "ExtractAlembic": {
+ "enabled": True,
+ "families": [
+ "pointcache",
+ "model",
+ "vrayproxy.alembic"
+ ]
+ },
+ "ExtractObj": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateRigContents": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateRigJointsHidden": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateRigControllers": {
+ "enabled": False,
+ "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": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateAssemblyName": {
+ "enabled": True,
+ "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": False,
+ "validate_shapes": True
+ },
+ "ExtractPlayblast": DEFAULT_PLAYBLAST_SETTING,
+ "ExtractMayaSceneRaw": {
+ "enabled": True,
+ "add_for_families": [
+ "layout"
+ ]
+ },
+ "ExtractCameraAlembic": {
+ "enabled": True,
+ "optional": True,
+ "active": True,
+ "bake_attributes": "[]"
+ },
+ "ExtractGLB": {
+ "enabled": True,
+ "active": True,
+ "ogsfx_path": "/maya2glTF/PBR/shaders/glTF_PBR.ogsfx"
+ },
+ "ExtractLook": {
+ "maketx_arguments": []
+ },
+ "ExtractGPUCache": {
+ "enabled": False,
+ "families": [
+ "model",
+ "animation",
+ "pointcache"
+ ],
+ "step": 1.0,
+ "stepSave": 1,
+ "optimize": True,
+ "optimizationThreshold": 40000,
+ "optimizeAnimationsForMotionBlur": True,
+ "writeMaterials": True,
+ "useBaseTessellation": True
+ }
+}
diff --git a/server_addon/maya/server/settings/render_settings.py b/server_addon/maya/server/settings/render_settings.py
new file mode 100644
index 0000000000..b6163a04ce
--- /dev/null
+++ b/server_addon/maya/server/settings/render_settings.py
@@ -0,0 +1,500 @@
+"""Providing models and values for Maya Render Settings."""
+from pydantic import Field
+
+from ayon_server.settings import BaseSettingsModel
+
+
+def aov_separators_enum():
+ return [
+ {"value": "dash", "label": "- (dash)"},
+ {"value": "underscore", "label": "_ (underscore)"},
+ {"value": "dot", "label": ". (dot)"}
+ ]
+
+
+def arnold_image_format_enum():
+ """Return enumerator for Arnold output formats."""
+ return [
+ {"label": "jpeg", "value": "jpeg"},
+ {"label": "png", "value": "png"},
+ {"label": "deepexr", "value": "deep exr"},
+ {"label": "tif", "value": "tif"},
+ {"label": "exr", "value": "exr"},
+ {"label": "maya", "value": "maya"},
+ {"label": "mtoa_shaders", "value": "mtoa_shaders"}
+ ]
+
+
+def arnold_aov_list_enum():
+ """Return enumerator for Arnold AOVs.
+
+ Note: Key is value, Value in this case is Label. This
+ was taken from v3 settings.
+ """
+ return [
+ {"value": "empty", "label": "< empty >"},
+ {"value": "ID", "label": "ID"},
+ {"value": "N", "label": "N"},
+ {"value": "P", "label": "P"},
+ {"value": "Pref", "label": "Pref"},
+ {"value": "RGBA", "label": "RGBA"},
+ {"value": "Z", "label": "Z"},
+ {"value": "albedo", "label": "albedo"},
+ {"value": "background", "label": "background"},
+ {"value": "coat", "label": "coat"},
+ {"value": "coat_albedo", "label": "coat_albedo"},
+ {"value": "coat_direct", "label": "coat_direct"},
+ {"value": "coat_indirect", "label": "coat_indirect"},
+ {"value": "cputime", "label": "cputime"},
+ {"value": "crypto_asset", "label": "crypto_asset"},
+ {"value": "crypto_material", "label": "cypto_material"},
+ {"value": "crypto_object", "label": "crypto_object"},
+ {"value": "diffuse", "label": "diffuse"},
+ {"value": "diffuse_albedo", "label": "diffuse_albedo"},
+ {"value": "diffuse_direct", "label": "diffuse_direct"},
+ {"value": "diffuse_indirect", "label": "diffuse_indirect"},
+ {"value": "direct", "label": "direct"},
+ {"value": "emission", "label": "emission"},
+ {"value": "highlight", "label": "highlight"},
+ {"value": "indirect", "label": "indirect"},
+ {"value": "motionvector", "label": "motionvector"},
+ {"value": "opacity", "label": "opacity"},
+ {"value": "raycount", "label": "raycount"},
+ {"value": "rim_light", "label": "rim_light"},
+ {"value": "shadow", "label": "shadow"},
+ {"value": "shadow_diff", "label": "shadow_diff"},
+ {"value": "shadow_mask", "label": "shadow_mask"},
+ {"value": "shadow_matte", "label": "shadow_matte"},
+ {"value": "sheen", "label": "sheen"},
+ {"value": "sheen_albedo", "label": "sheen_albedo"},
+ {"value": "sheen_direct", "label": "sheen_direct"},
+ {"value": "sheen_indirect", "label": "sheen_indirect"},
+ {"value": "specular", "label": "specular"},
+ {"value": "specular_albedo", "label": "specular_albedo"},
+ {"value": "specular_direct", "label": "specular_direct"},
+ {"value": "specular_indirect", "label": "specular_indirect"},
+ {"value": "sss", "label": "sss"},
+ {"value": "sss_albedo", "label": "sss_albedo"},
+ {"value": "sss_direct", "label": "sss_direct"},
+ {"value": "sss_indirect", "label": "sss_indirect"},
+ {"value": "transmission", "label": "transmission"},
+ {"value": "transmission_albedo", "label": "transmission_albedo"},
+ {"value": "transmission_direct", "label": "transmission_direct"},
+ {"value": "transmission_indirect", "label": "transmission_indirect"},
+ {"value": "volume", "label": "volume"},
+ {"value": "volume_Z", "label": "volume_Z"},
+ {"value": "volume_albedo", "label": "volume_albedo"},
+ {"value": "volume_direct", "label": "volume_direct"},
+ {"value": "volume_indirect", "label": "volume_indirect"},
+ {"value": "volume_opacity", "label": "volume_opacity"},
+ ]
+
+
+def vray_image_output_enum():
+ """Return output format for Vray enumerator."""
+ return [
+ {"label": "png", "value": "png"},
+ {"label": "jpg", "value": "jpg"},
+ {"label": "vrimg", "value": "vrimg"},
+ {"label": "hdr", "value": "hdr"},
+ {"label": "exr", "value": "exr"},
+ {"label": "exr (multichannel)", "value": "exr (multichannel)"},
+ {"label": "exr (deep)", "value": "exr (deep)"},
+ {"label": "tga", "value": "tga"},
+ {"label": "bmp", "value": "bmp"},
+ {"label": "sgi", "value": "sgi"}
+ ]
+
+
+def vray_aov_list_enum():
+ """Return enumerator for Vray AOVs.
+
+ Note: Key is value, Value in this case is Label. This
+ was taken from v3 settings.
+ """
+
+ return [
+ {"value": "empty", "label": "< empty >"},
+ {"value": "atmosphereChannel", "label": "atmosphere"},
+ {"value": "backgroundChannel", "label": "background"},
+ {"value": "bumpNormalsChannel", "label": "bumpnormals"},
+ {"value": "causticsChannel", "label": "caustics"},
+ {"value": "coatFilterChannel", "label": "coat_filter"},
+ {"value": "coatGlossinessChannel", "label": "coatGloss"},
+ {"value": "coatReflectionChannel", "label": "coat_reflection"},
+ {"value": "vrayCoatChannel", "label": "coat_specular"},
+ {"value": "CoverageChannel", "label": "coverage"},
+ {"value": "cryptomatteChannel", "label": "cryptomatte"},
+ {"value": "customColor", "label": "custom_color"},
+ {"value": "drBucketChannel", "label": "DR"},
+ {"value": "denoiserChannel", "label": "denoiser"},
+ {"value": "diffuseChannel", "label": "diffuse"},
+ {"value": "ExtraTexElement", "label": "extraTex"},
+ {"value": "giChannel", "label": "GI"},
+ {"value": "LightMixElement", "label": "None"},
+ {"value": "lightingChannel", "label": "lighting"},
+ {"value": "LightingAnalysisChannel", "label": "LightingAnalysis"},
+ {"value": "materialIDChannel", "label": "materialID"},
+ {"value": "MaterialSelectElement", "label": "materialSelect"},
+ {"value": "matteShadowChannel", "label": "matteShadow"},
+ {"value": "MultiMatteElement", "label": "multimatte"},
+ {"value": "multimatteIDChannel", "label": "multimatteID"},
+ {"value": "normalsChannel", "label": "normals"},
+ {"value": "nodeIDChannel", "label": "objectId"},
+ {"value": "objectSelectChannel", "label": "objectSelect"},
+ {"value": "rawCoatFilterChannel", "label": "raw_coat_filter"},
+ {"value": "rawCoatReflectionChannel", "label": "raw_coat_reflection"},
+ {"value": "rawDiffuseFilterChannel", "label": "rawDiffuseFilter"},
+ {"value": "rawGiChannel", "label": "rawGI"},
+ {"value": "rawLightChannel", "label": "rawLight"},
+ {"value": "rawReflectionChannel", "label": "rawReflection"},
+ {
+ "value": "rawReflectionFilterChannel",
+ "label": "rawReflectionFilter"
+ },
+ {"value": "rawRefractionChannel", "label": "rawRefraction"},
+ {
+ "value": "rawRefractionFilterChannel",
+ "label": "rawRefractionFilter"
+ },
+ {"value": "rawShadowChannel", "label": "rawShadow"},
+ {"value": "rawSheenFilterChannel", "label": "raw_sheen_filter"},
+ {
+ "value": "rawSheenReflectionChannel",
+ "label": "raw_sheen_reflection"
+ },
+ {"value": "rawTotalLightChannel", "label": "rawTotalLight"},
+ {"value": "reflectIORChannel", "label": "reflIOR"},
+ {"value": "reflectChannel", "label": "reflect"},
+ {"value": "reflectionFilterChannel", "label": "reflectionFilter"},
+ {"value": "reflectGlossinessChannel", "label": "reflGloss"},
+ {"value": "refractChannel", "label": "refract"},
+ {"value": "refractionFilterChannel", "label": "refractionFilter"},
+ {"value": "refractGlossinessChannel", "label": "refrGloss"},
+ {"value": "renderIDChannel", "label": "renderId"},
+ {"value": "FastSSS2Channel", "label": "SSS"},
+ {"value": "sampleRateChannel", "label": "sampleRate"},
+ {"value": "samplerInfo", "label": "samplerInfo"},
+ {"value": "selfIllumChannel", "label": "selfIllum"},
+ {"value": "shadowChannel", "label": "shadow"},
+ {"value": "sheenFilterChannel", "label": "sheen_filter"},
+ {"value": "sheenGlossinessChannel", "label": "sheenGloss"},
+ {"value": "sheenReflectionChannel", "label": "sheen_reflection"},
+ {"value": "vraySheenChannel", "label": "sheen_specular"},
+ {"value": "specularChannel", "label": "specular"},
+ {"value": "Toon", "label": "Toon"},
+ {"value": "toonLightingChannel", "label": "toonLighting"},
+ {"value": "toonSpecularChannel", "label": "toonSpecular"},
+ {"value": "totalLightChannel", "label": "totalLight"},
+ {"value": "unclampedColorChannel", "label": "unclampedColor"},
+ {"value": "VRScansPaintMaskChannel", "label": "VRScansPaintMask"},
+ {"value": "VRScansZoneMaskChannel", "label": "VRScansZoneMask"},
+ {"value": "velocityChannel", "label": "velocity"},
+ {"value": "zdepthChannel", "label": "zDepth"},
+ {"value": "LightSelectElement", "label": "lightselect"},
+ ]
+
+
+def redshift_engine_enum():
+ """Get Redshift engine type enumerator."""
+ return [
+ {"value": "0", "label": "None"},
+ {"value": "1", "label": "Photon Map"},
+ {"value": "2", "label": "Irradiance Cache"},
+ {"value": "3", "label": "Brute Force"}
+ ]
+
+
+def redshift_image_output_enum():
+ """Return output format for Redshift enumerator."""
+ return [
+ {"value": "iff", "label": "Maya IFF"},
+ {"value": "exr", "label": "OpenEXR"},
+ {"value": "tif", "label": "TIFF"},
+ {"value": "png", "label": "PNG"},
+ {"value": "tga", "label": "Targa"},
+ {"value": "jpg", "label": "JPEG"}
+ ]
+
+
+def redshift_aov_list_enum():
+ """Return enumerator for Vray AOVs.
+
+ Note: Key is value, Value in this case is Label. This
+ was taken from v3 settings.
+ """
+ return [
+ {"value": "empty", "label": "< none >"},
+ {"value": "AO", "label": "Ambient Occlusion"},
+ {"value": "Background", "label": "Background"},
+ {"value": "Beauty", "label": "Beauty"},
+ {"value": "BumpNormals", "label": "Bump Normals"},
+ {"value": "Caustics", "label": "Caustics"},
+ {"value": "CausticsRaw", "label": "Caustics Raw"},
+ {"value": "Cryptomatte", "label": "Cryptomatte"},
+ {"value": "Custom", "label": "Custom"},
+ {"value": "Z", "label": "Depth"},
+ {"value": "DiffuseFilter", "label": "Diffuse Filter"},
+ {"value": "DiffuseLighting", "label": "Diffuse Lighting"},
+ {"value": "DiffuseLightingRaw", "label": "Diffuse Lighting Raw"},
+ {"value": "Emission", "label": "Emission"},
+ {"value": "GI", "label": "Global Illumination"},
+ {"value": "GIRaw", "label": "Global Illumination Raw"},
+ {"value": "Matte", "label": "Matte"},
+ {"value": "MotionVectors", "label": "Ambient Occlusion"},
+ {"value": "N", "label": "Normals"},
+ {"value": "ID", "label": "ObjectID"},
+ {"value": "ObjectBumpNormal", "label": "Object-Space Bump Normals"},
+ {"value": "ObjectPosition", "label": "Object-Space Positions"},
+ {"value": "PuzzleMatte", "label": "Puzzle Matte"},
+ {"value": "Reflections", "label": "Reflections"},
+ {"value": "ReflectionsFilter", "label": "Reflections Filter"},
+ {"value": "ReflectionsRaw", "label": "Reflections Raw"},
+ {"value": "Refractions", "label": "Refractions"},
+ {"value": "RefractionsFilter", "label": "Refractions Filter"},
+ {"value": "RefractionsRaw", "label": "Refractions Filter"},
+ {"value": "Shadows", "label": "Shadows"},
+ {"value": "SpecularLighting", "label": "Specular Lighting"},
+ {"value": "SSS", "label": "Sub Surface Scatter"},
+ {"value": "SSSRaw", "label": "Sub Surface Scatter Raw"},
+ {
+ "value": "TotalDiffuseLightingRaw",
+ "label": "Total Diffuse Lighting Raw"
+ },
+ {
+ "value": "TotalTransLightingRaw",
+ "label": "Total Translucency Filter"
+ },
+ {"value": "TransTint", "label": "Translucency Filter"},
+ {"value": "TransGIRaw", "label": "Translucency Lighting Raw"},
+ {"value": "VolumeFogEmission", "label": "Volume Fog Emission"},
+ {"value": "VolumeFogTint", "label": "Volume Fog Tint"},
+ {"value": "VolumeLighting", "label": "Volume Lighting"},
+ {"value": "P", "label": "World Position"},
+ ]
+
+
+class AdditionalOptionsModel(BaseSettingsModel):
+ """Additional Option"""
+ _layout = "compact"
+
+ attribute: str = Field("", title="Attribute name")
+ value: str = Field("", title="Value")
+
+
+class ArnoldSettingsModel(BaseSettingsModel):
+ image_prefix: str = Field(title="Image prefix template")
+ image_format: str = Field(
+ enum_resolver=arnold_image_format_enum, title="Output Image Format")
+ multilayer_exr: bool = Field(title="Multilayer (exr)")
+ tiled: bool = Field(title="Tiled (tif, exr)")
+ aov_list: list[str] = Field(
+ default_factory=list,
+ enum_resolver=arnold_aov_list_enum,
+ title="AOVs to create"
+ )
+ additional_options: list[AdditionalOptionsModel] = Field(
+ default_factory=list,
+ title="Additional Arnold Options",
+ description=(
+ "Add additional options - put attribute and value, like AASamples"
+ )
+ )
+
+
+class VraySettingsModel(BaseSettingsModel):
+ image_prefix: str = Field(title="Image prefix template")
+ # engine was str because of JSON limitation (key must be string)
+ engine: str = Field(
+ enum_resolver=lambda: [
+ {"label": "V-Ray", "value": "1"},
+ {"label": "V-Ray GPU", "value": "2"}
+ ],
+ title="Production Engine"
+ )
+ image_format: str = Field(
+ enum_resolver=vray_image_output_enum,
+ title="Output Image Format"
+ )
+ aov_list: list[str] = Field(
+ default_factory=list,
+ enum_resolver=vray_aov_list_enum,
+ title="AOVs to create"
+ )
+ additional_options: list[AdditionalOptionsModel] = Field(
+ default_factory=list,
+ title="Additional Vray Options",
+ description=(
+ "Add additional options - put attribute and value,"
+ " like aaFilterSize"
+ )
+ )
+
+
+class RedshiftSettingsModel(BaseSettingsModel):
+ image_prefix: str = Field(title="Image prefix template")
+ # both engines are using the same enumerator,
+ # both were originally str because of JSON limitation.
+ primary_gi_engine: str = Field(
+ enum_resolver=redshift_engine_enum,
+ title="Primary GI Engine"
+ )
+ secondary_gi_engine: str = Field(
+ enum_resolver=redshift_engine_enum,
+ title="Secondary GI Engine"
+ )
+ image_format: str = Field(
+ enum_resolver=redshift_image_output_enum,
+ title="Output Image Format"
+ )
+ multilayer_exr: bool = Field(title="Multilayer (exr)")
+ force_combine: bool = Field(title="Force combine beauty and AOVs")
+ aov_list: list[str] = Field(
+ default_factory=list,
+ enum_resolver=redshift_aov_list_enum,
+ title="AOVs to create"
+ )
+ additional_options: list[AdditionalOptionsModel] = Field(
+ default_factory=list,
+ title="Additional Vray Options",
+ description=(
+ "Add additional options - put attribute and value,"
+ " like reflectionMaxTraceDepth"
+ )
+ )
+
+
+def renderman_display_filters():
+ return [
+ "PxrBackgroundDisplayFilter",
+ "PxrCopyAOVDisplayFilter",
+ "PxrEdgeDetect",
+ "PxrFilmicTonemapperDisplayFilter",
+ "PxrGradeDisplayFilter",
+ "PxrHalfBufferErrorFilter",
+ "PxrImageDisplayFilter",
+ "PxrLightSaturation",
+ "PxrShadowDisplayFilter",
+ "PxrStylizedHatching",
+ "PxrStylizedLines",
+ "PxrStylizedToon",
+ "PxrWhitePointDisplayFilter"
+ ]
+
+
+def renderman_sample_filters_enum():
+ return [
+ "PxrBackgroundSampleFilter",
+ "PxrCopyAOVSampleFilter",
+ "PxrCryptomatte",
+ "PxrFilmicTonemapperSampleFilter",
+ "PxrGradeSampleFilter",
+ "PxrShadowFilter",
+ "PxrWatermarkFilter",
+ "PxrWhitePointSampleFilter"
+ ]
+
+
+class RendermanSettingsModel(BaseSettingsModel):
+ image_prefix: str = Field(
+ "", title="Image prefix template")
+ image_dir: str = Field(
+ "", title="Image Output Directory")
+ display_filters: list[str] = Field(
+ default_factory=list,
+ title="Display Filters",
+ enum_resolver=renderman_display_filters
+ )
+ imageDisplay_dir: str = Field(
+ "", title="Image Display Filter Directory")
+ sample_filters: list[str] = Field(
+ default_factory=list,
+ title="Sample Filters",
+ enum_resolver=renderman_sample_filters_enum
+ )
+ cryptomatte_dir: str = Field(
+ "", title="Cryptomatte Output Directory")
+ watermark_dir: str = Field(
+ "", title="Watermark Filter Directory")
+ additional_options: list[AdditionalOptionsModel] = Field(
+ default_factory=list,
+ title="Additional Renderer Options"
+ )
+
+
+class RenderSettingsModel(BaseSettingsModel):
+ apply_render_settings: bool = Field(
+ title="Apply Render Settings on creation"
+ )
+ default_render_image_folder: str = Field(
+ title="Default render image folder"
+ )
+ enable_all_lights: bool = Field(
+ title="Include all lights in Render Setup Layers by default"
+ )
+ aov_separator: str = Field(
+ "underscore",
+ title="AOV Separator character",
+ enum_resolver=aov_separators_enum
+ )
+ reset_current_frame: bool = Field(
+ title="Reset Current Frame")
+ remove_aovs: bool = Field(
+ title="Remove existing AOVs")
+ arnold_renderer: ArnoldSettingsModel = Field(
+ default_factory=ArnoldSettingsModel,
+ title="Arnold Renderer")
+ vray_renderer: VraySettingsModel = Field(
+ default_factory=VraySettingsModel,
+ title="Vray Renderer")
+ redshift_renderer: RedshiftSettingsModel = Field(
+ default_factory=RedshiftSettingsModel,
+ title="Redshift Renderer")
+ renderman_renderer: RendermanSettingsModel = Field(
+ default_factory=RendermanSettingsModel,
+ title="Renderman Renderer")
+
+
+DEFAULT_RENDER_SETTINGS = {
+ "apply_render_settings": True,
+ "default_render_image_folder": "renders/maya",
+ "enable_all_lights": True,
+ "aov_separator": "underscore",
+ "reset_current_frame": False,
+ "remove_aovs": False,
+ "arnold_renderer": {
+ "image_prefix": "//_",
+ "image_format": "exr",
+ "multilayer_exr": True,
+ "tiled": True,
+ "aov_list": [],
+ "additional_options": []
+ },
+ "vray_renderer": {
+ "image_prefix": "//",
+ "engine": "1",
+ "image_format": "exr",
+ "aov_list": [],
+ "additional_options": []
+ },
+ "redshift_renderer": {
+ "image_prefix": "//",
+ "primary_gi_engine": "0",
+ "secondary_gi_engine": "0",
+ "image_format": "exr",
+ "multilayer_exr": True,
+ "force_combine": True,
+ "aov_list": [],
+ "additional_options": []
+ },
+ "renderman_renderer": {
+ "image_prefix": "{aov_separator}..",
+ "image_dir": "/",
+ "display_filters": [],
+ "imageDisplay_dir": "/{aov_separator}imageDisplayFilter..",
+ "sample_filters": [],
+ "cryptomatte_dir": "/{aov_separator}cryptomatte..",
+ "watermark_dir": "/{aov_separator}watermarkFilter..",
+ "additional_options": []
+ }
+}
diff --git a/server_addon/maya/server/settings/scriptsmenu.py b/server_addon/maya/server/settings/scriptsmenu.py
new file mode 100644
index 0000000000..82c1c2e53c
--- /dev/null
+++ b/server_addon/maya/server/settings/scriptsmenu.py
@@ -0,0 +1,43 @@
+from pydantic import Field
+
+from ayon_server.settings import BaseSettingsModel
+
+
+class ScriptsmenuSubmodel(BaseSettingsModel):
+ """Item Definition"""
+ _isGroup = True
+ type: str = Field(title="Type")
+ command: str = Field(title="Command")
+ sourcetype: str = Field(title="Source Type")
+ title: str = Field(title="Title")
+ tooltip: str = Field(title="Tooltip")
+ tags: list[str] = Field(default_factory=list, title="A list of tags")
+
+
+class ScriptsmenuModel(BaseSettingsModel):
+ _isGroup = True
+
+ name: str = Field(title="Menu Name")
+ definition: list[ScriptsmenuSubmodel] = Field(
+ default_factory=list,
+ title="Menu Definition",
+ description="Scriptmenu Items Definition"
+ )
+
+
+DEFAULT_SCRIPTSMENU_SETTINGS = {
+ "name": "OpenPype Tools",
+ "definition": [
+ {
+ "type": "action",
+ "command": "import openpype.hosts.maya.api.commands as op_cmds; op_cmds.edit_shader_definitions()",
+ "sourcetype": "python",
+ "title": "Edit shader name definitions",
+ "tooltip": "Edit shader name definitions used in validation and renaming.",
+ "tags": [
+ "pipeline",
+ "shader"
+ ]
+ }
+ ]
+}
diff --git a/server_addon/maya/server/settings/templated_workfile_settings.py b/server_addon/maya/server/settings/templated_workfile_settings.py
new file mode 100644
index 0000000000..ef81b31a07
--- /dev/null
+++ b/server_addon/maya/server/settings/templated_workfile_settings.py
@@ -0,0 +1,25 @@
+from pydantic import Field
+from ayon_server.settings import BaseSettingsModel, task_types_enum
+
+
+class WorkfileBuildProfilesModel(BaseSettingsModel):
+ _layout = "expanded"
+ task_types: list[str] = Field(
+ default_factory=list,
+ title="Task types",
+ enum_resolver=task_types_enum
+ )
+ task_names: list[str] = Field(default_factory=list, title="Task names")
+ path: str = Field("", title="Path to template")
+
+
+class TemplatedProfilesModel(BaseSettingsModel):
+ profiles: list[WorkfileBuildProfilesModel] = Field(
+ default_factory=list,
+ title="Profiles"
+ )
+
+
+DEFAULT_TEMPLATED_WORKFILE_SETTINGS = {
+ "profiles": []
+}
diff --git a/server_addon/maya/server/settings/workfile_build_settings.py b/server_addon/maya/server/settings/workfile_build_settings.py
new file mode 100644
index 0000000000..dc56d1a320
--- /dev/null
+++ b/server_addon/maya/server/settings/workfile_build_settings.py
@@ -0,0 +1,131 @@
+from pydantic import Field
+from ayon_server.settings import BaseSettingsModel, task_types_enum
+
+
+class ContextItemModel(BaseSettingsModel):
+ _layout = "expanded"
+ product_name_filters: list[str] = Field(
+ default_factory=list, title="Product name Filters")
+ product_types: list[str] = Field(
+ default_factory=list, title="Product types")
+ repre_names: list[str] = Field(
+ default_factory=list, title="Repre Names")
+ loaders: list[str] = Field(
+ default_factory=list, title="Loaders")
+
+
+class WorkfileSettingModel(BaseSettingsModel):
+ _layout = "expanded"
+ task_types: list[str] = Field(
+ default_factory=list,
+ enum_resolver=task_types_enum,
+ title="Task types")
+ tasks: list[str] = Field(
+ default_factory=list,
+ title="Task names")
+ current_context: list[ContextItemModel] = Field(
+ default_factory=list,
+ title="Current Context")
+ linked_assets: list[ContextItemModel] = Field(
+ default_factory=list,
+ title="Linked Assets")
+
+
+class ProfilesModel(BaseSettingsModel):
+ profiles: list[WorkfileSettingModel] = Field(
+ default_factory=list,
+ title="Profiles"
+ )
+
+
+DEFAULT_WORKFILE_SETTING = {
+ "profiles": [
+ {
+ "task_types": [],
+ "tasks": [
+ "Lighting"
+ ],
+ "current_context": [
+ {
+ "product_name_filters": [
+ ".+[Mm]ain"
+ ],
+ "product_types": [
+ "model"
+ ],
+ "repre_names": [
+ "abc",
+ "ma"
+ ],
+ "loaders": [
+ "ReferenceLoader"
+ ]
+ },
+ {
+ "product_name_filters": [],
+ "product_types": [
+ "animation",
+ "pointcache",
+ "proxyAbc"
+ ],
+ "repre_names": [
+ "abc"
+ ],
+ "loaders": [
+ "ReferenceLoader"
+ ]
+ },
+ {
+ "product_name_filters": [],
+ "product_types": [
+ "rendersetup"
+ ],
+ "repre_names": [
+ "json"
+ ],
+ "loaders": [
+ "RenderSetupLoader"
+ ]
+ },
+ {
+ "product_name_filters": [],
+ "product_types": [
+ "camera"
+ ],
+ "repre_names": [
+ "abc"
+ ],
+ "loaders": [
+ "ReferenceLoader"
+ ]
+ }
+ ],
+ "linked_assets": [
+ {
+ "product_name_filters": [],
+ "product_types": [
+ "sedress"
+ ],
+ "repre_names": [
+ "ma"
+ ],
+ "loaders": [
+ "ReferenceLoader"
+ ]
+ },
+ {
+ "product_name_filters": [],
+ "product_types": [
+ "ArnoldStandin"
+ ],
+ "repre_names": [
+ "ass"
+ ],
+ "loaders": [
+ "assLoader"
+ ]
+ }
+ ]
+ }
+ ]
+}
diff --git a/server_addon/maya/server/version.py b/server_addon/maya/server/version.py
new file mode 100644
index 0000000000..e57ad00718
--- /dev/null
+++ b/server_addon/maya/server/version.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+"""Package declaring addon version."""
+__version__ = "0.1.3"
diff --git a/server_addon/muster/server/__init__.py b/server_addon/muster/server/__init__.py
new file mode 100644
index 0000000000..2cb8943554
--- /dev/null
+++ b/server_addon/muster/server/__init__.py
@@ -0,0 +1,17 @@
+from typing import Type
+
+from ayon_server.addons import BaseServerAddon
+
+from .version import __version__
+from .settings import MusterSettings, DEFAULT_VALUES
+
+
+class MusterAddon(BaseServerAddon):
+ name = "muster"
+ version = __version__
+ title = "Muster"
+ settings_model: Type[MusterSettings] = MusterSettings
+
+ async def get_default_settings(self):
+ settings_model_cls = self.get_settings_model()
+ return settings_model_cls(**DEFAULT_VALUES)
diff --git a/server_addon/muster/server/settings.py b/server_addon/muster/server/settings.py
new file mode 100644
index 0000000000..e37c762870
--- /dev/null
+++ b/server_addon/muster/server/settings.py
@@ -0,0 +1,41 @@
+from pydantic import Field
+from ayon_server.settings import BaseSettingsModel
+
+
+class TemplatesMapping(BaseSettingsModel):
+ _layout = "compact"
+ name: str = Field(title="Name")
+ value: int = Field(title="mapping")
+
+
+class MusterSettings(BaseSettingsModel):
+ enabled: bool = True
+ MUSTER_REST_URL: str = Field(
+ "",
+ title="Muster Rest URL",
+ scope=["studio"],
+ )
+
+ templates_mapping: list[TemplatesMapping] = Field(
+ default_factory=list,
+ title="Templates mapping",
+ )
+
+
+DEFAULT_VALUES = {
+ "enabled": False,
+ "MUSTER_REST_URL": "http://127.0.0.1:9890",
+ "templates_mapping": [
+ {"name": "file_layers", "value": 7},
+ {"name": "mentalray", "value": 2},
+ {"name": "mentalray_sf", "value": 6},
+ {"name": "redshift", "value": 55},
+ {"name": "renderman", "value": 29},
+ {"name": "software", "value": 1},
+ {"name": "software_sf", "value": 5},
+ {"name": "turtle", "value": 10},
+ {"name": "vector", "value": 4},
+ {"name": "vray", "value": 37},
+ {"name": "ffmpeg", "value": 48}
+ ]
+}
diff --git a/server_addon/muster/server/version.py b/server_addon/muster/server/version.py
new file mode 100644
index 0000000000..485f44ac21
--- /dev/null
+++ b/server_addon/muster/server/version.py
@@ -0,0 +1 @@
+__version__ = "0.1.1"
diff --git a/server_addon/nuke/server/__init__.py b/server_addon/nuke/server/__init__.py
new file mode 100644
index 0000000000..032ceea5fb
--- /dev/null
+++ b/server_addon/nuke/server/__init__.py
@@ -0,0 +1,17 @@
+from typing import Type
+
+from ayon_server.addons import BaseServerAddon
+
+from .version import __version__
+from .settings import NukeSettings, DEFAULT_VALUES
+
+
+class NukeAddon(BaseServerAddon):
+ name = "nuke"
+ title = "Nuke"
+ version = __version__
+ settings_model: Type[NukeSettings] = NukeSettings
+
+ async def get_default_settings(self):
+ settings_model_cls = self.get_settings_model()
+ return settings_model_cls(**DEFAULT_VALUES)
diff --git a/server_addon/nuke/server/settings/__init__.py b/server_addon/nuke/server/settings/__init__.py
new file mode 100644
index 0000000000..1e58865395
--- /dev/null
+++ b/server_addon/nuke/server/settings/__init__.py
@@ -0,0 +1,10 @@
+from .main import (
+ NukeSettings,
+ DEFAULT_VALUES,
+)
+
+
+__all__ = (
+ "NukeSettings",
+ "DEFAULT_VALUES",
+)
diff --git a/server_addon/nuke/server/settings/common.py b/server_addon/nuke/server/settings/common.py
new file mode 100644
index 0000000000..700f01f3dc
--- /dev/null
+++ b/server_addon/nuke/server/settings/common.py
@@ -0,0 +1,142 @@
+import json
+from pydantic import Field
+from ayon_server.exceptions import BadRequestException
+from ayon_server.settings import BaseSettingsModel
+from ayon_server.types import (
+ ColorRGBA_float,
+ ColorRGB_uint8
+)
+
+
+def validate_json_dict(value):
+ if not value.strip():
+ return "{}"
+ try:
+ converted_value = json.loads(value)
+ success = isinstance(converted_value, dict)
+ except json.JSONDecodeError:
+ success = False
+
+ if not success:
+ raise BadRequestException(
+ "Environment's can't be parsed as json object"
+ )
+ return value
+
+
+class Vector2d(BaseSettingsModel):
+ _layout = "compact"
+
+ x: float = Field(1.0, title="X")
+ y: float = Field(1.0, title="Y")
+
+
+class Vector3d(BaseSettingsModel):
+ _layout = "compact"
+
+ x: float = Field(1.0, title="X")
+ y: float = Field(1.0, title="Y")
+ z: float = Field(1.0, title="Z")
+
+
+class Box(BaseSettingsModel):
+ _layout = "compact"
+
+ x: float = Field(1.0, title="X")
+ y: float = Field(1.0, title="Y")
+ r: float = Field(1.0, title="R")
+ t: float = Field(1.0, title="T")
+
+
+def formatable_knob_type_enum():
+ return [
+ {"value": "text", "label": "Text"},
+ {"value": "number", "label": "Number"},
+ {"value": "decimal_number", "label": "Decimal number"},
+ {"value": "2d_vector", "label": "2D vector"},
+ # "3D vector"
+ ]
+
+
+class Formatable(BaseSettingsModel):
+ _layout = "compact"
+
+ template: str = Field(
+ "",
+ placeholder="""{{key}} or {{key}};{{key}}""",
+ title="Template"
+ )
+ to_type: str = Field(
+ "Text",
+ title="To Knob type",
+ enum_resolver=formatable_knob_type_enum,
+ )
+
+
+knob_types_enum = [
+ {"value": "text", "label": "Text"},
+ {"value": "formatable", "label": "Formate from template"},
+ {"value": "color_gui", "label": "Color GUI"},
+ {"value": "boolean", "label": "Boolean"},
+ {"value": "number", "label": "Number"},
+ {"value": "decimal_number", "label": "Decimal number"},
+ {"value": "vector_2d", "label": "2D vector"},
+ {"value": "vector_3d", "label": "3D vector"},
+ {"value": "color", "label": "Color"},
+ {"value": "box", "label": "Box"},
+ {"value": "expression", "label": "Expression"}
+]
+
+
+class KnobModel(BaseSettingsModel):
+ """# TODO: new data structure
+ - v3 was having type, name, value but
+ ayon is not able to make it the same. Current model is
+ defining `type` as `text` and instead of `value` the key is `text`.
+ So if `type` is `boolean` then key is `boolean` (value).
+ """
+ _layout = "expanded"
+
+ type: str = Field(
+ title="Type",
+ description="Switch between different knob types",
+ enum_resolver=lambda: knob_types_enum,
+ conditionalEnum=True
+ )
+
+ name: str = Field(
+ title="Name",
+ placeholder="Name"
+ )
+ text: str = Field("", title="Value")
+ color_gui: ColorRGB_uint8 = Field(
+ (0, 0, 255),
+ title="RGB Uint8",
+ )
+ boolean: bool = Field(False, title="Value")
+ number: int = Field(0, title="Value")
+ decimal_number: float = Field(0.0, title="Value")
+ vector_2d: Vector2d = Field(
+ default_factory=Vector2d,
+ title="Value"
+ )
+ vector_3d: Vector3d = Field(
+ default_factory=Vector3d,
+ title="Value"
+ )
+ color: ColorRGBA_float = Field(
+ (0.0, 0.0, 1.0, 1.0),
+ title="RGBA Float"
+ )
+ box: Box = Field(
+ default_factory=Box,
+ title="Value"
+ )
+ formatable: Formatable = Field(
+ default_factory=Formatable,
+ title="Formatable"
+ )
+ expression: str = Field(
+ "",
+ title="Expression"
+ )
diff --git a/server_addon/nuke/server/settings/create_plugins.py b/server_addon/nuke/server/settings/create_plugins.py
new file mode 100644
index 0000000000..0bbae4ee77
--- /dev/null
+++ b/server_addon/nuke/server/settings/create_plugins.py
@@ -0,0 +1,223 @@
+from pydantic import validator, Field
+from ayon_server.settings import (
+ BaseSettingsModel,
+ ensure_unique_names
+)
+from .common import KnobModel
+
+
+def instance_attributes_enum():
+ """Return create write instance attributes."""
+ return [
+ {"value": "reviewable", "label": "Reviewable"},
+ {"value": "farm_rendering", "label": "Farm rendering"},
+ {"value": "use_range_limit", "label": "Use range limit"}
+ ]
+
+
+class PrenodeModel(BaseSettingsModel):
+ # TODO: missing in host api
+ # - good for `dependency`
+ name: str = Field(
+ title="Node name"
+ )
+
+ # TODO: `nodeclass` should be renamed to `nuke_node_class`
+ nodeclass: str = Field(
+ "",
+ title="Node class"
+ )
+ dependent: str = Field(
+ "",
+ title="Incoming dependency"
+ )
+
+ """# TODO: Changes in host api:
+ - Need complete rework of knob types in nuke integration.
+ - We could not support v3 style of settings.
+ """
+ knobs: list[KnobModel] = Field(
+ title="Knobs",
+ )
+
+ @validator("knobs")
+ def ensure_unique_names(cls, value):
+ """Ensure name fields within the lists have unique names."""
+ ensure_unique_names(value)
+ return value
+
+
+class CreateWriteRenderModel(BaseSettingsModel):
+ temp_rendering_path_template: str = Field(
+ title="Temporary rendering path template"
+ )
+ default_variants: list[str] = Field(
+ title="Default variants",
+ default_factory=list
+ )
+ instance_attributes: list[str] = Field(
+ default_factory=list,
+ enum_resolver=instance_attributes_enum,
+ title="Instance attributes"
+ )
+
+ """# TODO: Changes in host api:
+ - prenodes key was originally dict and now is list
+ (we could not support v3 style of settings)
+ """
+ prenodes: list[PrenodeModel] = Field(
+ title="Preceding nodes",
+ )
+
+ @validator("prenodes")
+ def ensure_unique_names(cls, value):
+ """Ensure name fields within the lists have unique names."""
+ ensure_unique_names(value)
+ return value
+
+
+class CreateWritePrerenderModel(BaseSettingsModel):
+ temp_rendering_path_template: str = Field(
+ title="Temporary rendering path template"
+ )
+ default_variants: list[str] = Field(
+ title="Default variants",
+ default_factory=list
+ )
+ instance_attributes: list[str] = Field(
+ default_factory=list,
+ enum_resolver=instance_attributes_enum,
+ title="Instance attributes"
+ )
+
+ """# TODO: Changes in host api:
+ - prenodes key was originally dict and now is list
+ (we could not support v3 style of settings)
+ """
+ prenodes: list[PrenodeModel] = Field(
+ title="Preceding nodes",
+ )
+
+ @validator("prenodes")
+ def ensure_unique_names(cls, value):
+ """Ensure name fields within the lists have unique names."""
+ ensure_unique_names(value)
+ return value
+
+
+class CreateWriteImageModel(BaseSettingsModel):
+ temp_rendering_path_template: str = Field(
+ title="Temporary rendering path template"
+ )
+ default_variants: list[str] = Field(
+ title="Default variants",
+ default_factory=list
+ )
+ instance_attributes: list[str] = Field(
+ default_factory=list,
+ enum_resolver=instance_attributes_enum,
+ title="Instance attributes"
+ )
+
+ """# TODO: Changes in host api:
+ - prenodes key was originally dict and now is list
+ (we could not support v3 style of settings)
+ """
+ prenodes: list[PrenodeModel] = Field(
+ title="Preceding nodes",
+ )
+
+ @validator("prenodes")
+ def ensure_unique_names(cls, value):
+ """Ensure name fields within the lists have unique names."""
+ ensure_unique_names(value)
+ return value
+
+
+class CreatorPluginsSettings(BaseSettingsModel):
+ CreateWriteRender: CreateWriteRenderModel = Field(
+ default_factory=CreateWriteRenderModel,
+ title="Create Write Render"
+ )
+ CreateWritePrerender: CreateWritePrerenderModel = Field(
+ default_factory=CreateWritePrerenderModel,
+ title="Create Write Prerender"
+ )
+ CreateWriteImage: CreateWriteImageModel = Field(
+ default_factory=CreateWriteImageModel,
+ title="Create Write Image"
+ )
+
+
+DEFAULT_CREATE_SETTINGS = {
+ "CreateWriteRender": {
+ "temp_rendering_path_template": "{work}/renders/nuke/{product[name]}/{product[name]}.{frame}.{ext}",
+ "default_variants": [
+ "Main",
+ "Mask"
+ ],
+ "instance_attributes": [
+ "reviewable",
+ "farm_rendering"
+ ],
+ "prenodes": [
+ {
+ "name": "Reformat01",
+ "nodeclass": "Reformat",
+ "dependent": "",
+ "knobs": [
+ {
+ "type": "text",
+ "name": "resize",
+ "text": "none"
+ },
+ {
+ "type": "boolean",
+ "name": "black_outside",
+ "boolean": True
+ }
+ ]
+ }
+ ]
+ },
+ "CreateWritePrerender": {
+ "temp_rendering_path_template": "{work}/renders/nuke/{product[name]}/{product[name]}.{frame}.{ext}",
+ "default_variants": [
+ "Key01",
+ "Bg01",
+ "Fg01",
+ "Branch01",
+ "Part01"
+ ],
+ "instance_attributes": [
+ "farm_rendering",
+ "use_range_limit"
+ ],
+ "prenodes": []
+ },
+ "CreateWriteImage": {
+ "temp_rendering_path_template": "{work}/renders/nuke/{product[name]}/{product[name]}.{ext}",
+ "default_variants": [
+ "StillFrame",
+ "MPFrame",
+ "LayoutFrame"
+ ],
+ "instance_attributes": [
+ "use_range_limit"
+ ],
+ "prenodes": [
+ {
+ "name": "FrameHold01",
+ "nodeclass": "FrameHold",
+ "dependent": "",
+ "knobs": [
+ {
+ "type": "expression",
+ "name": "first_frame",
+ "expression": "parent.first"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/server_addon/nuke/server/settings/dirmap.py b/server_addon/nuke/server/settings/dirmap.py
new file mode 100644
index 0000000000..2da6d7bf60
--- /dev/null
+++ b/server_addon/nuke/server/settings/dirmap.py
@@ -0,0 +1,47 @@
+from pydantic import Field
+from ayon_server.settings import BaseSettingsModel
+
+
+class DirmapPathsSubmodel(BaseSettingsModel):
+ _layout = "compact"
+ source_path: list[str] = Field(
+ default_factory=list,
+ title="Source Paths"
+ )
+ destination_path: list[str] = Field(
+ default_factory=list,
+ title="Destination Paths"
+ )
+
+
+class DirmapSettings(BaseSettingsModel):
+ """Nuke color management project settings."""
+ _isGroup: bool = True
+
+ enabled: bool = Field(title="enabled")
+ paths: DirmapPathsSubmodel = Field(
+ default_factory=DirmapPathsSubmodel,
+ title="Dirmap Paths"
+ )
+
+
+"""# TODO:
+nuke is having originally implemented
+following data inputs:
+
+"nuke-dirmap": {
+ "enabled": false,
+ "paths": {
+ "source-path": [],
+ "destination-path": []
+ }
+}
+"""
+
+DEFAULT_DIRMAP_SETTINGS = {
+ "enabled": False,
+ "paths": {
+ "source_path": [],
+ "destination_path": []
+ }
+}
diff --git a/server_addon/nuke/server/settings/filters.py b/server_addon/nuke/server/settings/filters.py
new file mode 100644
index 0000000000..7e2702b3b7
--- /dev/null
+++ b/server_addon/nuke/server/settings/filters.py
@@ -0,0 +1,19 @@
+from pydantic import Field, validator
+from ayon_server.settings import BaseSettingsModel, ensure_unique_names
+
+
+class PublishGUIFilterItemModel(BaseSettingsModel):
+ _layout = "compact"
+ name: str = Field(title="Name")
+ value: bool = Field(True, title="Active")
+
+
+class PublishGUIFiltersModel(BaseSettingsModel):
+ _layout = "compact"
+ name: str = Field(title="Name")
+ value: list[PublishGUIFilterItemModel] = Field(default_factory=list)
+
+ @validator("value")
+ def validate_unique_outputs(cls, value):
+ ensure_unique_names(value)
+ return value
diff --git a/server_addon/nuke/server/settings/general.py b/server_addon/nuke/server/settings/general.py
new file mode 100644
index 0000000000..bcbb183952
--- /dev/null
+++ b/server_addon/nuke/server/settings/general.py
@@ -0,0 +1,42 @@
+from pydantic import Field
+from ayon_server.settings import BaseSettingsModel
+
+
+class MenuShortcut(BaseSettingsModel):
+ """Nuke general project settings."""
+
+ create: str = Field(
+ title="Create..."
+ )
+ publish: str = Field(
+ title="Publish..."
+ )
+ load: str = Field(
+ title="Load..."
+ )
+ manage: str = Field(
+ title="Manage..."
+ )
+ build_workfile: str = Field(
+ title="Build Workfile..."
+ )
+
+
+class GeneralSettings(BaseSettingsModel):
+ """Nuke general project settings."""
+
+ menu: MenuShortcut = Field(
+ default_factory=MenuShortcut,
+ title="Menu Shortcuts",
+ )
+
+
+DEFAULT_GENERAL_SETTINGS = {
+ "menu": {
+ "create": "ctrl+alt+c",
+ "publish": "ctrl+alt+p",
+ "load": "ctrl+alt+l",
+ "manage": "ctrl+alt+m",
+ "build_workfile": "ctrl+alt+b"
+ }
+}
diff --git a/server_addon/nuke/server/settings/gizmo.py b/server_addon/nuke/server/settings/gizmo.py
new file mode 100644
index 0000000000..4cdd614da8
--- /dev/null
+++ b/server_addon/nuke/server/settings/gizmo.py
@@ -0,0 +1,79 @@
+from pydantic import Field
+from ayon_server.settings import (
+ BaseSettingsModel,
+ MultiplatformPathModel,
+ MultiplatformPathListModel,
+)
+
+
+class SubGizmoItem(BaseSettingsModel):
+ title: str = Field(
+ title="Label"
+ )
+ sourcetype: str = Field(
+ title="Type of usage"
+ )
+ command: str = Field(
+ title="Python command"
+ )
+ icon: str = Field(
+ title="Icon Path"
+ )
+ shortcut: str = Field(
+ title="Hotkey"
+ )
+
+
+class GizmoDefinitionItem(BaseSettingsModel):
+ gizmo_toolbar_path: str = Field(
+ title="Gizmo Menu"
+ )
+ sub_gizmo_list: list[SubGizmoItem] = Field(
+ default_factory=list, title="Sub Gizmo List")
+
+
+class GizmoItem(BaseSettingsModel):
+ """Nuke gizmo item """
+
+ toolbar_menu_name: str = Field(
+ title="Toolbar Menu Name"
+ )
+ gizmo_source_dir: MultiplatformPathListModel = Field(
+ default_factory=MultiplatformPathListModel,
+ title="Gizmo Directory Path"
+ )
+ toolbar_icon_path: MultiplatformPathModel = Field(
+ default_factory=MultiplatformPathModel,
+ title="Toolbar Icon Path"
+ )
+ gizmo_definition: list[GizmoDefinitionItem] = Field(
+ default_factory=list, title="Gizmo Definition")
+
+
+DEFAULT_GIZMO_ITEM = {
+ "toolbar_menu_name": "OpenPype Gizmo",
+ "gizmo_source_dir": {
+ "windows": [],
+ "darwin": [],
+ "linux": []
+ },
+ "toolbar_icon_path": {
+ "windows": "",
+ "darwin": "",
+ "linux": ""
+ },
+ "gizmo_definition": [
+ {
+ "gizmo_toolbar_path": "/path/to/menu",
+ "sub_gizmo_list": [
+ {
+ "sourcetype": "python",
+ "title": "Gizmo Note",
+ "command": "nuke.nodes.StickyNote(label='You can create your own toolbar menu in the Nuke GizmoMenu of OpenPype')",
+ "icon": "",
+ "shortcut": ""
+ }
+ ]
+ }
+ ]
+}
diff --git a/server_addon/nuke/server/settings/imageio.py b/server_addon/nuke/server/settings/imageio.py
new file mode 100644
index 0000000000..b43017ef8b
--- /dev/null
+++ b/server_addon/nuke/server/settings/imageio.py
@@ -0,0 +1,410 @@
+from typing import Literal
+from pydantic import validator, Field
+from ayon_server.settings import (
+ BaseSettingsModel,
+ ensure_unique_names,
+)
+
+from .common import KnobModel
+
+
+class NodesModel(BaseSettingsModel):
+ """# TODO: This needs to be somehow labeled in settings panel
+ or at least it could show gist of configuration
+ """
+ _layout = "expanded"
+ plugins: list[str] = Field(
+ title="Used in plugins"
+ )
+ # TODO: rename `nukeNodeClass` to `nuke_node_class`
+ nukeNodeClass: str = Field(
+ title="Nuke Node Class",
+ )
+
+ """ # TODO: Need complete rework of knob types
+ in nuke integration. We could not support v3 style of settings.
+ """
+ knobs: list[KnobModel] = Field(
+ title="Knobs",
+ )
+
+ @validator("knobs")
+ def ensure_unique_names(cls, value):
+ """Ensure name fields within the lists have unique names."""
+ ensure_unique_names(value)
+ return value
+
+
+class NodesSetting(BaseSettingsModel):
+ # TODO: rename `requiredNodes` to `required_nodes`
+ requiredNodes: list[NodesModel] = Field(
+ title="Plugin required",
+ default_factory=list
+ )
+ # TODO: rename `overrideNodes` to `override_nodes`
+ overrideNodes: list[NodesModel] = Field(
+ title="Plugin's node overrides",
+ default_factory=list
+ )
+
+
+def ocio_configs_switcher_enum():
+ return [
+ {"value": "nuke-default", "label": "nuke-default"},
+ {"value": "spi-vfx", "label": "spi-vfx"},
+ {"value": "spi-anim", "label": "spi-anim"},
+ {"value": "aces_0.1.1", "label": "aces_0.1.1"},
+ {"value": "aces_0.7.1", "label": "aces_0.7.1"},
+ {"value": "aces_1.0.1", "label": "aces_1.0.1"},
+ {"value": "aces_1.0.3", "label": "aces_1.0.3"},
+ {"value": "aces_1.1", "label": "aces_1.1"},
+ {"value": "aces_1.2", "label": "aces_1.2"},
+ {"value": "aces_1.3", "label": "aces_1.3"},
+ {"value": "custom", "label": "custom"}
+ ]
+
+
+class WorkfileColorspaceSettings(BaseSettingsModel):
+ """Nuke workfile colorspace preset. """
+ """# TODO: enhance settings with host api:
+ we need to add mapping to resolve properly keys.
+ Nuke is excpecting camel case key names,
+ but for better code consistency we need to
+ be using snake_case:
+
+ color_management = colorManagement
+ ocio_config = OCIO_config
+ working_space_name = workingSpaceLUT
+ monitor_name = monitorLut
+ monitor_out_name = monitorOutLut
+ int_8_name = int8Lut
+ int_16_name = int16Lut
+ log_name = logLut
+ float_name = floatLut
+ """
+
+ colorManagement: Literal["Nuke", "OCIO"] = Field(
+ title="Color Management"
+ )
+
+ OCIO_config: str = Field(
+ title="OpenColorIO Config",
+ description="Switch between OCIO configs",
+ enum_resolver=ocio_configs_switcher_enum,
+ conditionalEnum=True
+ )
+
+ workingSpaceLUT: str = Field(
+ title="Working Space"
+ )
+ monitorLut: str = Field(
+ title="Monitor"
+ )
+ int8Lut: str = Field(
+ title="8-bit files"
+ )
+ int16Lut: str = Field(
+ title="16-bit files"
+ )
+ logLut: str = Field(
+ title="Log files"
+ )
+ floatLut: str = Field(
+ title="Float files"
+ )
+
+
+class ReadColorspaceRulesItems(BaseSettingsModel):
+ _layout = "expanded"
+
+ regex: str = Field("", title="Regex expression")
+ colorspace: str = Field("", title="Colorspace")
+
+
+class RegexInputsModel(BaseSettingsModel):
+ inputs: list[ReadColorspaceRulesItems] = Field(
+ default_factory=list,
+ title="Inputs"
+ )
+
+
+class ViewProcessModel(BaseSettingsModel):
+ viewerProcess: str = Field(
+ title="Viewer Process Name"
+ )
+
+
+class ImageIOConfigModel(BaseSettingsModel):
+ override_global_config: bool = Field(
+ False,
+ title="Override global OCIO config"
+ )
+ filepath: list[str] = Field(
+ default_factory=list,
+ title="Config path"
+ )
+
+
+class ImageIOFileRuleModel(BaseSettingsModel):
+ name: str = Field("", title="Rule name")
+ pattern: str = Field("", title="Regex pattern")
+ colorspace: str = Field("", title="Colorspace name")
+ ext: str = Field("", title="File extension")
+
+
+class ImageIOFileRulesModel(BaseSettingsModel):
+ activate_host_rules: bool = Field(False)
+ rules: list[ImageIOFileRuleModel] = Field(
+ default_factory=list,
+ title="Rules"
+ )
+
+ @validator("rules")
+ def validate_unique_outputs(cls, value):
+ ensure_unique_names(value)
+ return value
+
+
+class ImageIOSettings(BaseSettingsModel):
+ """Nuke color management project settings. """
+ _isGroup: bool = True
+
+ """# TODO: enhance settings with host api:
+ to restruture settings for simplification.
+
+ now: nuke/imageio/viewer/viewerProcess
+ future: nuke/imageio/viewer
+ """
+ activate_host_color_management: bool = Field(
+ True, title="Enable Color Management")
+ ocio_config: ImageIOConfigModel = Field(
+ default_factory=ImageIOConfigModel,
+ title="OCIO config"
+ )
+ file_rules: ImageIOFileRulesModel = Field(
+ default_factory=ImageIOFileRulesModel,
+ title="File Rules"
+ )
+ viewer: ViewProcessModel = Field(
+ default_factory=ViewProcessModel,
+ title="Viewer",
+ description="""Viewer profile is used during
+ Creation of new viewer node at knob viewerProcess"""
+ )
+
+ """# TODO: enhance settings with host api:
+ to restruture settings for simplification.
+
+ now: nuke/imageio/baking/viewerProcess
+ future: nuke/imageio/baking
+ """
+ baking: ViewProcessModel = Field(
+ default_factory=ViewProcessModel,
+ title="Baking",
+ description="""Baking profile is used during
+ publishing baked colorspace data at knob viewerProcess"""
+ )
+
+ workfile: WorkfileColorspaceSettings = Field(
+ default_factory=WorkfileColorspaceSettings,
+ title="Workfile"
+ )
+
+ nodes: NodesSetting = Field(
+ default_factory=NodesSetting,
+ title="Nodes"
+ )
+ """# TODO: enhance settings with host api:
+ - old settings are using `regexInputs` key but we
+ need to rename to `regex_inputs`
+ - no need for `inputs` middle part. It can stay
+ directly on `regex_inputs`
+ """
+ regexInputs: RegexInputsModel = Field(
+ default_factory=RegexInputsModel,
+ title="Assign colorspace to read nodes via rules"
+ )
+
+
+DEFAULT_IMAGEIO_SETTINGS = {
+ "viewer": {
+ "viewerProcess": "sRGB"
+ },
+ "baking": {
+ "viewerProcess": "rec709"
+ },
+ "workfile": {
+ "colorManagement": "Nuke",
+ "OCIO_config": "nuke-default",
+ "workingSpaceLUT": "linear",
+ "monitorLut": "sRGB",
+ "int8Lut": "sRGB",
+ "int16Lut": "sRGB",
+ "logLut": "Cineon",
+ "floatLut": "linear"
+ },
+ "nodes": {
+ "requiredNodes": [
+ {
+ "plugins": [
+ "CreateWriteRender"
+ ],
+ "nukeNodeClass": "Write",
+ "knobs": [
+ {
+ "type": "text",
+ "name": "file_type",
+ "text": "exr"
+ },
+ {
+ "type": "text",
+ "name": "datatype",
+ "text": "16 bit half"
+ },
+ {
+ "type": "text",
+ "name": "compression",
+ "text": "Zip (1 scanline)"
+ },
+ {
+ "type": "boolean",
+ "name": "autocrop",
+ "boolean": True
+ },
+ {
+ "type": "color_gui",
+ "name": "tile_color",
+ "color_gui": [
+ 186,
+ 35,
+ 35
+ ]
+ },
+ {
+ "type": "text",
+ "name": "channels",
+ "text": "rgb"
+ },
+ {
+ "type": "text",
+ "name": "colorspace",
+ "text": "linear"
+ },
+ {
+ "type": "boolean",
+ "name": "create_directories",
+ "boolean": True
+ }
+ ]
+ },
+ {
+ "plugins": [
+ "CreateWritePrerender"
+ ],
+ "nukeNodeClass": "Write",
+ "knobs": [
+ {
+ "type": "text",
+ "name": "file_type",
+ "text": "exr"
+ },
+ {
+ "type": "text",
+ "name": "datatype",
+ "text": "16 bit half"
+ },
+ {
+ "type": "text",
+ "name": "compression",
+ "text": "Zip (1 scanline)"
+ },
+ {
+ "type": "boolean",
+ "name": "autocrop",
+ "boolean": True
+ },
+ {
+ "type": "color_gui",
+ "name": "tile_color",
+ "color_gui": [
+ 171,
+ 171,
+ 10
+ ]
+ },
+ {
+ "type": "text",
+ "name": "channels",
+ "text": "rgb"
+ },
+ {
+ "type": "text",
+ "name": "colorspace",
+ "text": "linear"
+ },
+ {
+ "type": "boolean",
+ "name": "create_directories",
+ "boolean": True
+ }
+ ]
+ },
+ {
+ "plugins": [
+ "CreateWriteImage"
+ ],
+ "nukeNodeClass": "Write",
+ "knobs": [
+ {
+ "type": "text",
+ "name": "file_type",
+ "text": "tiff"
+ },
+ {
+ "type": "text",
+ "name": "datatype",
+ "text": "16 bit"
+ },
+ {
+ "type": "text",
+ "name": "compression",
+ "text": "Deflate"
+ },
+ {
+ "type": "color_gui",
+ "name": "tile_color",
+ "color_gui": [
+ 56,
+ 162,
+ 7
+ ]
+ },
+ {
+ "type": "text",
+ "name": "channels",
+ "text": "rgb"
+ },
+ {
+ "type": "text",
+ "name": "colorspace",
+ "text": "sRGB"
+ },
+ {
+ "type": "boolean",
+ "name": "create_directories",
+ "boolean": True
+ }
+ ]
+ }
+ ],
+ "overrideNodes": []
+ },
+ "regexInputs": {
+ "inputs": [
+ {
+ "regex": "(beauty).*(?=.exr)",
+ "colorspace": "linear"
+ }
+ ]
+ }
+}
diff --git a/server_addon/nuke/server/settings/loader_plugins.py b/server_addon/nuke/server/settings/loader_plugins.py
new file mode 100644
index 0000000000..6db381bffb
--- /dev/null
+++ b/server_addon/nuke/server/settings/loader_plugins.py
@@ -0,0 +1,80 @@
+from pydantic import Field
+from ayon_server.settings import BaseSettingsModel
+
+
+class LoadImageModel(BaseSettingsModel):
+ enabled: bool = Field(
+ title="Enabled"
+ )
+ """# TODO: v3 api used `_representation`
+ New api is hiding it so it had to be renamed
+ to `representations_include`
+ """
+ representations_include: list[str] = Field(
+ default_factory=list,
+ title="Include representations"
+ )
+
+ node_name_template: str = Field(
+ title="Read node name template"
+ )
+
+
+class LoadClipOptionsModel(BaseSettingsModel):
+ start_at_workfile: bool = Field(
+ title="Start at workfile's start frame"
+ )
+ add_retime: bool = Field(
+ title="Add retime"
+ )
+
+
+class LoadClipModel(BaseSettingsModel):
+ enabled: bool = Field(
+ title="Enabled"
+ )
+ """# TODO: v3 api used `_representation`
+ New api is hiding it so it had to be renamed
+ to `representations_include`
+ """
+ representations_include: list[str] = Field(
+ default_factory=list,
+ title="Include representations"
+ )
+
+ node_name_template: str = Field(
+ title="Read node name template"
+ )
+ options_defaults: LoadClipOptionsModel = Field(
+ default_factory=LoadClipOptionsModel,
+ title="Loader option defaults"
+ )
+
+
+class LoaderPuginsModel(BaseSettingsModel):
+ LoadImage: LoadImageModel = Field(
+ default_factory=LoadImageModel,
+ title="Load Image"
+ )
+ LoadClip: LoadClipModel = Field(
+ default_factory=LoadClipModel,
+ title="Load Clip"
+ )
+
+
+DEFAULT_LOADER_PLUGINS_SETTINGS = {
+ "LoadImage": {
+ "enabled": True,
+ "representations_include": [],
+ "node_name_template": "{class_name}_{ext}"
+ },
+ "LoadClip": {
+ "enabled": True,
+ "representations_include": [],
+ "node_name_template": "{class_name}_{ext}",
+ "options_defaults": {
+ "start_at_workfile": True,
+ "add_retime": True
+ }
+ }
+}
diff --git a/server_addon/nuke/server/settings/main.py b/server_addon/nuke/server/settings/main.py
new file mode 100644
index 0000000000..4687d48ac9
--- /dev/null
+++ b/server_addon/nuke/server/settings/main.py
@@ -0,0 +1,128 @@
+from pydantic import validator, Field
+
+from ayon_server.settings import (
+ BaseSettingsModel,
+ ensure_unique_names
+)
+
+from .general import (
+ GeneralSettings,
+ DEFAULT_GENERAL_SETTINGS
+)
+from .imageio import (
+ ImageIOSettings,
+ DEFAULT_IMAGEIO_SETTINGS
+)
+from .dirmap import (
+ DirmapSettings,
+ DEFAULT_DIRMAP_SETTINGS
+)
+from .scriptsmenu import (
+ ScriptsmenuSettings,
+ DEFAULT_SCRIPTSMENU_SETTINGS
+)
+from .gizmo import (
+ GizmoItem,
+ DEFAULT_GIZMO_ITEM
+)
+from .create_plugins import (
+ CreatorPluginsSettings,
+ DEFAULT_CREATE_SETTINGS
+)
+from .publish_plugins import (
+ PublishPuginsModel,
+ DEFAULT_PUBLISH_PLUGIN_SETTINGS
+)
+from .loader_plugins import (
+ LoaderPuginsModel,
+ DEFAULT_LOADER_PLUGINS_SETTINGS
+)
+from .workfile_builder import (
+ WorkfileBuilderModel,
+ DEFAULT_WORKFILE_BUILDER_SETTINGS
+)
+from .templated_workfile_build import (
+ TemplatedWorkfileBuildModel
+)
+from .filters import PublishGUIFilterItemModel
+
+
+class NukeSettings(BaseSettingsModel):
+ """Nuke addon settings."""
+
+ general: GeneralSettings = Field(
+ default_factory=GeneralSettings,
+ title="General",
+ )
+
+ imageio: ImageIOSettings = Field(
+ default_factory=ImageIOSettings,
+ title="Color Management (imageio)",
+ )
+ """# TODO: fix host api:
+ - rename `nuke-dirmap` to `dirmap` was inevitable
+ """
+ dirmap: DirmapSettings = Field(
+ default_factory=DirmapSettings,
+ title="Nuke Directory Mapping",
+ )
+
+ scriptsmenu: ScriptsmenuSettings = Field(
+ default_factory=ScriptsmenuSettings,
+ title="Scripts Menu Definition",
+ )
+
+ gizmo: list[GizmoItem] = Field(
+ default_factory=list, title="Gizmo Menu")
+
+ create: CreatorPluginsSettings = Field(
+ default_factory=CreatorPluginsSettings,
+ title="Creator Plugins",
+ )
+
+ publish: PublishPuginsModel = Field(
+ default_factory=PublishPuginsModel,
+ title="Publish Plugins",
+ )
+
+ load: LoaderPuginsModel = Field(
+ default_factory=LoaderPuginsModel,
+ title="Loader Plugins",
+ )
+
+ workfile_builder: WorkfileBuilderModel = Field(
+ default_factory=WorkfileBuilderModel,
+ title="Workfile Builder",
+ )
+
+ templated_workfile_build: TemplatedWorkfileBuildModel = Field(
+ title="Templated Workfile Build",
+ default_factory=TemplatedWorkfileBuildModel
+ )
+
+ filters: list[PublishGUIFilterItemModel] = Field(
+ default_factory=list
+ )
+
+ @validator("filters")
+ def ensure_unique_names(cls, value):
+ """Ensure name fields within the lists have unique names."""
+ ensure_unique_names(value)
+ return value
+
+
+DEFAULT_VALUES = {
+ "general": DEFAULT_GENERAL_SETTINGS,
+ "imageio": DEFAULT_IMAGEIO_SETTINGS,
+ "dirmap": DEFAULT_DIRMAP_SETTINGS,
+ "scriptsmenu": DEFAULT_SCRIPTSMENU_SETTINGS,
+ "gizmo": [DEFAULT_GIZMO_ITEM],
+ "create": DEFAULT_CREATE_SETTINGS,
+ "publish": DEFAULT_PUBLISH_PLUGIN_SETTINGS,
+ "load": DEFAULT_LOADER_PLUGINS_SETTINGS,
+ "workfile_builder": DEFAULT_WORKFILE_BUILDER_SETTINGS,
+ "templated_workfile_build": {
+ "profiles": []
+ },
+ "filters": []
+}
diff --git a/server_addon/nuke/server/settings/publish_plugins.py b/server_addon/nuke/server/settings/publish_plugins.py
new file mode 100644
index 0000000000..7e898f8c9a
--- /dev/null
+++ b/server_addon/nuke/server/settings/publish_plugins.py
@@ -0,0 +1,504 @@
+from pydantic import validator, Field
+from ayon_server.settings import (
+ BaseSettingsModel,
+ ensure_unique_names,
+ task_types_enum
+)
+from .common import KnobModel, validate_json_dict
+
+
+def nuke_render_publish_types_enum():
+ """Return all nuke render families available in creators."""
+ return [
+ {"value": "render", "label": "Render"},
+ {"value": "prerender", "label": "Prerender"},
+ {"value": "image", "label": "Image"}
+ ]
+
+
+def nuke_product_types_enum():
+ """Return all nuke families available in creators."""
+ return [
+ {"value": "nukenodes", "label": "Nukenodes"},
+ {"value": "model", "label": "Model"},
+ {"value": "camera", "label": "Camera"},
+ {"value": "gizmo", "label": "Gizmo"},
+ {"value": "source", "label": "Source"}
+ ] + nuke_render_publish_types_enum()
+
+
+class NodeModel(BaseSettingsModel):
+ # TODO: missing in host api
+ name: str = Field(
+ title="Node name"
+ )
+ # TODO: `nodeclass` rename to `nuke_node_class`
+ nodeclass: str = Field(
+ "",
+ title="Node class"
+ )
+ dependent: str = Field(
+ "",
+ title="Incoming dependency"
+ )
+ """# TODO: Changes in host api:
+ - Need complete rework of knob types in nuke integration.
+ - We could not support v3 style of settings.
+ """
+ knobs: list[KnobModel] = Field(
+ title="Knobs",
+ )
+
+ @validator("knobs")
+ def ensure_unique_names(cls, value):
+ """Ensure name fields within the lists have unique names."""
+ ensure_unique_names(value)
+ return value
+
+
+class ThumbnailRepositionNodeModel(BaseSettingsModel):
+ node_class: str = Field(title="Node class")
+ knobs: list[KnobModel] = Field(title="Knobs", default_factory=list)
+
+ @validator("knobs")
+ def ensure_unique_names(cls, value):
+ """Ensure name fields within the lists have unique names."""
+ ensure_unique_names(value)
+ return value
+
+
+class CollectInstanceDataModel(BaseSettingsModel):
+ sync_workfile_version_on_product_types: list[str] = Field(
+ default_factory=list,
+ enum_resolver=nuke_product_types_enum,
+ title="Sync workfile versions for familes"
+ )
+
+
+class OptionalPluginModel(BaseSettingsModel):
+ enabled: bool = Field(True)
+ optional: bool = Field(title="Optional")
+ active: bool = Field(title="Active")
+
+
+class ValidateKnobsModel(BaseSettingsModel):
+ enabled: bool = Field(title="Enabled")
+ knobs: str = Field(
+ "{}",
+ title="Knobs",
+ widget="textarea",
+ )
+
+ @validator("knobs")
+ def validate_json(cls, value):
+ return validate_json_dict(value)
+
+
+class ExtractThumbnailModel(BaseSettingsModel):
+ enabled: bool = Field(title="Enabled")
+ use_rendered: bool = Field(title="Use rendered images")
+ bake_viewer_process: bool = Field(title="Bake view process")
+ bake_viewer_input_process: bool = Field(title="Bake viewer input process")
+ """# TODO: needs to rewrite from v3 to ayon
+ - `nodes` in v3 was dict but now `prenodes` is list of dict
+ - also later `nodes` should be `prenodes`
+ """
+
+ nodes: list[NodeModel] = Field(
+ title="Nodes (deprecated)"
+ )
+ reposition_nodes: list[ThumbnailRepositionNodeModel] = Field(
+ title="Reposition nodes",
+ default_factory=list
+ )
+
+
+class ExtractReviewDataModel(BaseSettingsModel):
+ enabled: bool = Field(title="Enabled")
+
+
+class ExtractReviewDataLutModel(BaseSettingsModel):
+ enabled: bool = Field(title="Enabled")
+
+
+class BakingStreamFilterModel(BaseSettingsModel):
+ task_types: list[str] = Field(
+ default_factory=list,
+ title="Task types",
+ enum_resolver=task_types_enum
+ )
+ product_types: list[str] = Field(
+ default_factory=list,
+ enum_resolver=nuke_render_publish_types_enum,
+ title="Sync workfile versions for familes"
+ )
+ product_names: list[str] = Field(
+ default_factory=list, title="Product names")
+
+
+class ReformatNodesRepositionNodes(BaseSettingsModel):
+ node_class: str = Field(title="Node class")
+ knobs: list[KnobModel] = Field(
+ default_factory=list,
+ title="Node knobs")
+
+
+class ReformatNodesConfigModel(BaseSettingsModel):
+ """Only reposition nodes supported.
+
+ You can add multiple reformat nodes and set their knobs.
+ Order of reformat nodes is important. First reformat node will
+ be applied first and last reformat node will be applied last.
+ """
+ enabled: bool = Field(False)
+ reposition_nodes: list[ReformatNodesRepositionNodes] = Field(
+ default_factory=list,
+ title="Reposition knobs"
+ )
+
+
+class BakingStreamModel(BaseSettingsModel):
+ name: str = Field(title="Output name")
+ filter: BakingStreamFilterModel = Field(
+ title="Filter", default_factory=BakingStreamFilterModel)
+ read_raw: bool = Field(title="Read raw switch")
+ viewer_process_override: str = Field(title="Viewer process override")
+ bake_viewer_process: bool = Field(title="Bake view process")
+ bake_viewer_input_process: bool = Field(title="Bake viewer input process")
+ reformat_nodes_config: ReformatNodesConfigModel = Field(
+ default_factory=ReformatNodesConfigModel,
+ title="Reformat Nodes")
+ extension: str = Field(title="File extension")
+ add_custom_tags: list[str] = Field(
+ title="Custom tags", default_factory=list)
+
+
+class ExtractReviewDataMovModel(BaseSettingsModel):
+ enabled: bool = Field(title="Enabled")
+ viewer_lut_raw: bool = Field(title="Viewer lut raw")
+ outputs: list[BakingStreamModel] = Field(
+ title="Baking streams"
+ )
+
+
+class FSubmissionNoteModel(BaseSettingsModel):
+ enabled: bool = Field(title="enabled")
+ template: str = Field(title="Template")
+
+
+class FSubmistingForModel(BaseSettingsModel):
+ enabled: bool = Field(title="enabled")
+ template: str = Field(title="Template")
+
+
+class FVFXScopeOfWorkModel(BaseSettingsModel):
+ enabled: bool = Field(title="enabled")
+ template: str = Field(title="Template")
+
+
+class ExctractSlateFrameParamModel(BaseSettingsModel):
+ f_submission_note: FSubmissionNoteModel = Field(
+ title="f_submission_note",
+ default_factory=FSubmissionNoteModel
+ )
+ f_submitting_for: FSubmistingForModel = Field(
+ title="f_submitting_for",
+ default_factory=FSubmistingForModel
+ )
+ f_vfx_scope_of_work: FVFXScopeOfWorkModel = Field(
+ title="f_vfx_scope_of_work",
+ default_factory=FVFXScopeOfWorkModel
+ )
+
+
+class ExtractSlateFrameModel(BaseSettingsModel):
+ viewer_lut_raw: bool = Field(title="Viewer lut raw")
+ """# TODO: v3 api different model:
+ - not possible to replicate v3 model:
+ {"name": [bool, str]}
+ - not it is:
+ {"name": {"enabled": bool, "template": str}}
+ """
+ key_value_mapping: ExctractSlateFrameParamModel = Field(
+ title="Key value mapping",
+ default_factory=ExctractSlateFrameParamModel
+ )
+
+
+class IncrementScriptVersionModel(BaseSettingsModel):
+ enabled: bool = Field(title="Enabled")
+ optional: bool = Field(title="Optional")
+ active: bool = Field(title="Active")
+
+
+class PublishPuginsModel(BaseSettingsModel):
+ CollectInstanceData: CollectInstanceDataModel = Field(
+ title="Collect Instance Version",
+ default_factory=CollectInstanceDataModel,
+ section="Collectors"
+ )
+ ValidateCorrectAssetName: OptionalPluginModel = Field(
+ title="Validate Correct Folder Name",
+ default_factory=OptionalPluginModel,
+ section="Validators"
+ )
+ ValidateContainers: OptionalPluginModel = Field(
+ title="Validate Containers",
+ default_factory=OptionalPluginModel
+ )
+ ValidateKnobs: ValidateKnobsModel = Field(
+ title="Validate Knobs",
+ default_factory=ValidateKnobsModel
+ )
+ ValidateOutputResolution: OptionalPluginModel = Field(
+ title="Validate Output Resolution",
+ default_factory=OptionalPluginModel
+ )
+ ValidateGizmo: OptionalPluginModel = Field(
+ title="Validate Gizmo",
+ default_factory=OptionalPluginModel
+ )
+ ValidateBackdrop: OptionalPluginModel = Field(
+ title="Validate Backdrop",
+ default_factory=OptionalPluginModel
+ )
+ ValidateScript: OptionalPluginModel = Field(
+ title="Validate Script",
+ default_factory=OptionalPluginModel
+ )
+ ExtractThumbnail: ExtractThumbnailModel = Field(
+ title="Extract Thumbnail",
+ default_factory=ExtractThumbnailModel,
+ section="Extractors"
+ )
+ ExtractReviewData: ExtractReviewDataModel = Field(
+ title="Extract Review Data",
+ default_factory=ExtractReviewDataModel
+ )
+ ExtractReviewDataLut: ExtractReviewDataLutModel = Field(
+ title="Extract Review Data Lut",
+ default_factory=ExtractReviewDataLutModel
+ )
+ ExtractReviewDataMov: ExtractReviewDataMovModel = Field(
+ title="Extract Review Data Mov",
+ default_factory=ExtractReviewDataMovModel
+ )
+ ExtractSlateFrame: ExtractSlateFrameModel = Field(
+ title="Extract Slate Frame",
+ default_factory=ExtractSlateFrameModel
+ )
+ # TODO: plugin should be renamed - `workfile` not `script`
+ IncrementScriptVersion: IncrementScriptVersionModel = Field(
+ title="Increment Workfile Version",
+ default_factory=IncrementScriptVersionModel,
+ section="Integrators"
+ )
+
+
+DEFAULT_PUBLISH_PLUGIN_SETTINGS = {
+ "CollectInstanceData": {
+ "sync_workfile_version_on_product_types": [
+ "nukenodes",
+ "camera",
+ "gizmo",
+ "source",
+ "render",
+ "write"
+ ]
+ },
+ "ValidateCorrectAssetName": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateContainers": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateKnobs": {
+ "enabled": False,
+ "knobs": "\n".join([
+ '{',
+ ' "render": {',
+ ' "review": true',
+ ' }',
+ '}'
+ ])
+ },
+ "ValidateOutputResolution": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateGizmo": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateBackdrop": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateScript": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ExtractThumbnail": {
+ "enabled": True,
+ "use_rendered": True,
+ "bake_viewer_process": True,
+ "bake_viewer_input_process": True,
+ "nodes": [
+ {
+ "name": "Reformat01",
+ "nodeclass": "Reformat",
+ "dependency": "",
+ "knobs": [
+ {
+ "type": "text",
+ "name": "type",
+ "text": "to format"
+ },
+ {
+ "type": "text",
+ "name": "format",
+ "text": "HD_1080"
+ },
+ {
+ "type": "text",
+ "name": "filter",
+ "text": "Lanczos6"
+ },
+ {
+ "type": "boolean",
+ "name": "black_outside",
+ "boolean": True
+ },
+ {
+ "type": "boolean",
+ "name": "pbb",
+ "boolean": False
+ }
+ ]
+ }
+ ],
+ "reposition_nodes": [
+ {
+ "node_class": "Reformat",
+ "knobs": [
+ {
+ "type": "text",
+ "name": "type",
+ "text": "to format"
+ },
+ {
+ "type": "text",
+ "name": "format",
+ "text": "HD_1080"
+ },
+ {
+ "type": "text",
+ "name": "filter",
+ "text": "Lanczos6"
+ },
+ {
+ "type": "bool",
+ "name": "black_outside",
+ "boolean": True
+ },
+ {
+ "type": "bool",
+ "name": "pbb",
+ "boolean": False
+ }
+ ]
+ }
+ ]
+ },
+ "ExtractReviewData": {
+ "enabled": False
+ },
+ "ExtractReviewDataLut": {
+ "enabled": False
+ },
+ "ExtractReviewDataMov": {
+ "enabled": True,
+ "viewer_lut_raw": False,
+ "outputs": [
+ {
+ "name": "baking",
+ "filter": {
+ "task_types": [],
+ "product_types": [],
+ "product_names": []
+ },
+ "read_raw": False,
+ "viewer_process_override": "",
+ "bake_viewer_process": True,
+ "bake_viewer_input_process": True,
+ "reformat_nodes_config": {
+ "enabled": False,
+ "reposition_nodes": [
+ {
+ "node_class": "Reformat",
+ "knobs": [
+ {
+ "type": "text",
+ "name": "type",
+ "text": "to format"
+ },
+ {
+ "type": "text",
+ "name": "format",
+ "text": "HD_1080"
+ },
+ {
+ "type": "text",
+ "name": "filter",
+ "text": "Lanczos6"
+ },
+ {
+ "type": "bool",
+ "name": "black_outside",
+ "boolean": True
+ },
+ {
+ "type": "bool",
+ "name": "pbb",
+ "boolean": False
+ }
+ ]
+ }
+ ]
+ },
+ "extension": "mov",
+ "add_custom_tags": []
+ }
+ ]
+ },
+ "ExtractSlateFrame": {
+ "viewer_lut_raw": False,
+ "key_value_mapping": {
+ "f_submission_note": {
+ "enabled": True,
+ "template": "{comment}"
+ },
+ "f_submitting_for": {
+ "enabled": True,
+ "template": "{intent[value]}"
+ },
+ "f_vfx_scope_of_work": {
+ "enabled": False,
+ "template": ""
+ }
+ }
+ },
+ "IncrementScriptVersion": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ }
+}
diff --git a/server_addon/nuke/server/settings/scriptsmenu.py b/server_addon/nuke/server/settings/scriptsmenu.py
new file mode 100644
index 0000000000..9d1c32ebac
--- /dev/null
+++ b/server_addon/nuke/server/settings/scriptsmenu.py
@@ -0,0 +1,54 @@
+from pydantic import Field
+from ayon_server.settings import BaseSettingsModel
+
+
+class ScriptsmenuSubmodel(BaseSettingsModel):
+ """Item Definition"""
+ _isGroup = True
+
+ type: str = Field(title="Type")
+ command: str = Field(title="Command")
+ sourcetype: str = Field(title="Source Type")
+ title: str = Field(title="Title")
+ tooltip: str = Field(title="Tooltip")
+
+
+class ScriptsmenuSettings(BaseSettingsModel):
+ """Nuke script menu project settings."""
+ _isGroup = True
+
+ # TODO: in api rename key `name` to `menu_name`
+ name: str = Field(title="Menu Name")
+ definition: list[ScriptsmenuSubmodel] = Field(
+ default_factory=list,
+ title="Definition",
+ description="Scriptmenu Items Definition"
+ )
+
+
+DEFAULT_SCRIPTSMENU_SETTINGS = {
+ "name": "OpenPype Tools",
+ "definition": [
+ {
+ "type": "action",
+ "sourcetype": "python",
+ "title": "OpenPype Docs",
+ "command": "import webbrowser;webbrowser.open(url='https://openpype.io/docs/artist_hosts_nuke_tut')",
+ "tooltip": "Open the OpenPype Nuke user doc page"
+ },
+ {
+ "type": "action",
+ "sourcetype": "python",
+ "title": "Set Frame Start (Read Node)",
+ "command": "from openpype.hosts.nuke.startup.frame_setting_for_read_nodes import main;main();",
+ "tooltip": "Set frame start for read node(s)"
+ },
+ {
+ "type": "action",
+ "sourcetype": "python",
+ "title": "Set non publish output for Write Node",
+ "command": "from openpype.hosts.nuke.startup.custom_write_node import main;main();",
+ "tooltip": "Open the OpenPype Nuke user doc page"
+ }
+ ]
+}
diff --git a/server_addon/nuke/server/settings/templated_workfile_build.py b/server_addon/nuke/server/settings/templated_workfile_build.py
new file mode 100644
index 0000000000..e0245c8d06
--- /dev/null
+++ b/server_addon/nuke/server/settings/templated_workfile_build.py
@@ -0,0 +1,33 @@
+from pydantic import Field
+from ayon_server.settings import (
+ BaseSettingsModel,
+ task_types_enum,
+)
+
+
+class TemplatedWorkfileProfileModel(BaseSettingsModel):
+ task_types: list[str] = Field(
+ default_factory=list,
+ title="Task types",
+ enum_resolver=task_types_enum
+ )
+ task_names: list[str] = Field(
+ default_factory=list,
+ title="Task names"
+ )
+ path: str = Field(
+ title="Path to template"
+ )
+ keep_placeholder: bool = Field(
+ False,
+ title="Keep placeholders")
+ create_first_version: bool = Field(
+ True,
+ title="Create first version"
+ )
+
+
+class TemplatedWorkfileBuildModel(BaseSettingsModel):
+ profiles: list[TemplatedWorkfileProfileModel] = Field(
+ default_factory=list
+ )
diff --git a/server_addon/nuke/server/settings/workfile_builder.py b/server_addon/nuke/server/settings/workfile_builder.py
new file mode 100644
index 0000000000..ee67c7c16a
--- /dev/null
+++ b/server_addon/nuke/server/settings/workfile_builder.py
@@ -0,0 +1,72 @@
+from pydantic import Field
+from ayon_server.settings import (
+ BaseSettingsModel,
+ task_types_enum,
+ MultiplatformPathModel,
+)
+
+
+class CustomTemplateModel(BaseSettingsModel):
+ task_types: list[str] = Field(
+ default_factory=list,
+ title="Task types",
+ enum_resolver=task_types_enum
+ )
+ path: MultiplatformPathModel = Field(
+ default_factory=MultiplatformPathModel,
+ title="Gizmo Directory Path"
+ )
+
+
+class BuilderProfileItemModel(BaseSettingsModel):
+ product_name_filters: list[str] = Field(
+ default_factory=list,
+ title="Product name"
+ )
+ product_types: list[str] = Field(
+ default_factory=list,
+ title="Product types"
+ )
+ repre_names: list[str] = Field(
+ default_factory=list,
+ title="Representations"
+ )
+ loaders: list[str] = Field(
+ default_factory=list,
+ title="Loader plugins"
+ )
+
+
+class BuilderProfileModel(BaseSettingsModel):
+ task_types: list[str] = Field(
+ default_factory=list,
+ title="Task types",
+ enum_resolver=task_types_enum
+ )
+ tasks: list[str] = Field(
+ default_factory=list,
+ title="Task names"
+ )
+ current_context: list[BuilderProfileItemModel] = Field(
+ title="Current context")
+ linked_assets: list[BuilderProfileItemModel] = Field(
+ title="Linked assets/shots")
+
+
+class WorkfileBuilderModel(BaseSettingsModel):
+ create_first_version: bool = Field(
+ title="Create first workfile")
+ custom_templates: list[CustomTemplateModel] = Field(
+ title="Custom templates")
+ builder_on_start: bool = Field(
+ title="Run Builder at first workfile")
+ profiles: list[BuilderProfileModel] = Field(
+ title="Builder profiles")
+
+
+DEFAULT_WORKFILE_BUILDER_SETTINGS = {
+ "create_first_version": False,
+ "custom_templates": [],
+ "builder_on_start": False,
+ "profiles": []
+}
diff --git a/server_addon/nuke/server/version.py b/server_addon/nuke/server/version.py
new file mode 100644
index 0000000000..b3f4756216
--- /dev/null
+++ b/server_addon/nuke/server/version.py
@@ -0,0 +1 @@
+__version__ = "0.1.2"
diff --git a/server_addon/openpype/client/pyproject.toml b/server_addon/openpype/client/pyproject.toml
new file mode 100644
index 0000000000..6d5ac92ca7
--- /dev/null
+++ b/server_addon/openpype/client/pyproject.toml
@@ -0,0 +1,25 @@
+[project]
+name="openpype"
+description="OpenPype addon for AYON server."
+
+[tool.poetry.dependencies]
+python = ">=3.9.1,<3.10"
+aiohttp_json_rpc = "*" # TVPaint server
+aiohttp-middlewares = "^2.0.0"
+wsrpc_aiohttp = "^3.1.1" # websocket server
+clique = "1.6.*"
+shotgun_api3 = {git = "https://github.com/shotgunsoftware/python-api.git", rev = "v3.3.3"}
+gazu = "^0.9.3"
+google-api-python-client = "^1.12.8" # sync server google support (should be separate?)
+jsonschema = "^2.6.0"
+pymongo = "^3.11.2"
+log4mongo = "^1.7"
+pathlib2= "^2.3.5" # deadline submit publish job only (single place, maybe not needed?)
+pyblish-base = "^1.8.11"
+pynput = "^1.7.2" # Timers manager - TODO replace
+"Qt.py" = "^1.3.3"
+qtawesome = "0.7.3"
+speedcopy = "^2.1"
+slack-sdk = "^3.6.0"
+pysftp = "^0.2.9"
+dropbox = "^11.20.0"
diff --git a/server_addon/openpype/server/__init__.py b/server_addon/openpype/server/__init__.py
new file mode 100644
index 0000000000..df24c73c76
--- /dev/null
+++ b/server_addon/openpype/server/__init__.py
@@ -0,0 +1,9 @@
+from ayon_server.addons import BaseServerAddon
+
+from .version import __version__
+
+
+class OpenPypeAddon(BaseServerAddon):
+ name = "openpype"
+ title = "OpenPype"
+ version = __version__
diff --git a/server_addon/photoshop/LICENSE b/server_addon/photoshop/LICENSE
new file mode 100644
index 0000000000..d645695673
--- /dev/null
+++ b/server_addon/photoshop/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/server_addon/photoshop/README.md b/server_addon/photoshop/README.md
new file mode 100644
index 0000000000..2d1e1c745c
--- /dev/null
+++ b/server_addon/photoshop/README.md
@@ -0,0 +1,4 @@
+Photoshp Addon
+===============
+
+Integration with Adobe Photoshop.
diff --git a/server_addon/photoshop/server/__init__.py b/server_addon/photoshop/server/__init__.py
new file mode 100644
index 0000000000..3a45f7a809
--- /dev/null
+++ b/server_addon/photoshop/server/__init__.py
@@ -0,0 +1,16 @@
+from ayon_server.addons import BaseServerAddon
+
+from .settings import PhotoshopSettings, DEFAULT_PHOTOSHOP_SETTING
+from .version import __version__
+
+
+class Photoshop(BaseServerAddon):
+ name = "photoshop"
+ title = "Photoshop"
+ version = __version__
+
+ settings_model = PhotoshopSettings
+
+ async def get_default_settings(self):
+ settings_model_cls = self.get_settings_model()
+ return settings_model_cls(**DEFAULT_PHOTOSHOP_SETTING)
diff --git a/server_addon/photoshop/server/settings/__init__.py b/server_addon/photoshop/server/settings/__init__.py
new file mode 100644
index 0000000000..9ae5764362
--- /dev/null
+++ b/server_addon/photoshop/server/settings/__init__.py
@@ -0,0 +1,10 @@
+from .main import (
+ PhotoshopSettings,
+ DEFAULT_PHOTOSHOP_SETTING,
+)
+
+
+__all__ = (
+ "PhotoshopSettings",
+ "DEFAULT_PHOTOSHOP_SETTING",
+)
diff --git a/server_addon/photoshop/server/settings/creator_plugins.py b/server_addon/photoshop/server/settings/creator_plugins.py
new file mode 100644
index 0000000000..2fe63a7e3a
--- /dev/null
+++ b/server_addon/photoshop/server/settings/creator_plugins.py
@@ -0,0 +1,79 @@
+from pydantic import Field
+
+from ayon_server.settings import BaseSettingsModel
+
+
+class CreateImagePluginModel(BaseSettingsModel):
+ enabled: bool = Field(True, title="Enabled")
+ active_on_create: bool = Field(True, title="Active by default")
+ mark_for_review: bool = Field(False, title="Review by default")
+ default_variants: list[str] = Field(
+ default_factory=list,
+ title="Default Variants"
+ )
+
+
+class AutoImageCreatorPluginModel(BaseSettingsModel):
+ enabled: bool = Field(False, title="Enabled")
+ active_on_create: bool = Field(True, title="Active by default")
+ mark_for_review: bool = Field(False, title="Review by default")
+ default_variant: str = Field("", title="Default Variants")
+
+
+class CreateReviewPlugin(BaseSettingsModel):
+ enabled: bool = Field(True, title="Enabled")
+ active_on_create: bool = Field(True, title="Active by default")
+ default_variant: str = Field("", title="Default Variants")
+
+
+class CreateWorkfilelugin(BaseSettingsModel):
+ enabled: bool = Field(True, title="Enabled")
+ active_on_create: bool = Field(True, title="Active by default")
+ default_variant: str = Field("", title="Default Variants")
+
+
+class PhotoshopCreatorPlugins(BaseSettingsModel):
+ ImageCreator: CreateImagePluginModel = Field(
+ title="Create Image",
+ default_factory=CreateImagePluginModel,
+ )
+ AutoImageCreator: AutoImageCreatorPluginModel = Field(
+ title="Create Flatten Image",
+ default_factory=AutoImageCreatorPluginModel,
+ )
+ ReviewCreator: CreateReviewPlugin = Field(
+ title="Create Review",
+ default_factory=CreateReviewPlugin,
+ )
+ WorkfileCreator: CreateWorkfilelugin = Field(
+ title="Create Workfile",
+ default_factory=CreateWorkfilelugin,
+ )
+
+
+DEFAULT_CREATE_SETTINGS = {
+ "ImageCreator": {
+ "enabled": True,
+ "active_on_create": True,
+ "mark_for_review": False,
+ "default_variants": [
+ "Main"
+ ]
+ },
+ "AutoImageCreator": {
+ "enabled": False,
+ "active_on_create": True,
+ "mark_for_review": False,
+ "default_variant": ""
+ },
+ "ReviewCreator": {
+ "enabled": True,
+ "active_on_create": True,
+ "default_variant": ""
+ },
+ "WorkfileCreator": {
+ "enabled": True,
+ "active_on_create": True,
+ "default_variant": "Main"
+ }
+}
diff --git a/server_addon/photoshop/server/settings/imageio.py b/server_addon/photoshop/server/settings/imageio.py
new file mode 100644
index 0000000000..56b7f2fa32
--- /dev/null
+++ b/server_addon/photoshop/server/settings/imageio.py
@@ -0,0 +1,64 @@
+from pydantic import Field, validator
+from ayon_server.settings import BaseSettingsModel
+from ayon_server.settings.validators import ensure_unique_names
+
+
+class ImageIOConfigModel(BaseSettingsModel):
+ override_global_config: bool = Field(
+ False,
+ title="Override global OCIO config"
+ )
+ filepath: list[str] = Field(
+ default_factory=list,
+ title="Config path"
+ )
+
+
+class ImageIOFileRuleModel(BaseSettingsModel):
+ name: str = Field("", title="Rule name")
+ pattern: str = Field("", title="Regex pattern")
+ colorspace: str = Field("", title="Colorspace name")
+ ext: str = Field("", title="File extension")
+
+
+class ImageIOFileRulesModel(BaseSettingsModel):
+ activate_host_rules: bool = Field(False)
+ rules: list[ImageIOFileRuleModel] = Field(
+ default_factory=list,
+ title="Rules"
+ )
+
+ @validator("rules")
+ def validate_unique_outputs(cls, value):
+ ensure_unique_names(value)
+ return value
+
+
+class ImageIORemappingRulesModel(BaseSettingsModel):
+ host_native_name: str = Field(
+ title="Application native colorspace name"
+ )
+ ocio_name: str = Field(title="OCIO colorspace name")
+
+
+class ImageIORemappingModel(BaseSettingsModel):
+ rules: list[ImageIORemappingRulesModel] = Field(
+ default_factory=list)
+
+
+class PhotoshopImageIOModel(BaseSettingsModel):
+ activate_host_color_management: bool = Field(
+ True, title="Enable Color Management"
+ )
+ remapping: ImageIORemappingModel = Field(
+ title="Remapping colorspace names",
+ default_factory=ImageIORemappingModel
+ )
+ ocio_config: ImageIOConfigModel = Field(
+ default_factory=ImageIOConfigModel,
+ title="OCIO config"
+ )
+ file_rules: ImageIOFileRulesModel = Field(
+ default_factory=ImageIOFileRulesModel,
+ title="File Rules"
+ )
diff --git a/server_addon/photoshop/server/settings/main.py b/server_addon/photoshop/server/settings/main.py
new file mode 100644
index 0000000000..ae7705b3db
--- /dev/null
+++ b/server_addon/photoshop/server/settings/main.py
@@ -0,0 +1,41 @@
+from pydantic import Field
+from ayon_server.settings import BaseSettingsModel
+
+from .imageio import PhotoshopImageIOModel
+from .creator_plugins import PhotoshopCreatorPlugins, DEFAULT_CREATE_SETTINGS
+from .publish_plugins import PhotoshopPublishPlugins, DEFAULT_PUBLISH_SETTINGS
+from .workfile_builder import WorkfileBuilderPlugin
+
+
+class PhotoshopSettings(BaseSettingsModel):
+ """Photoshop Project Settings."""
+
+ imageio: PhotoshopImageIOModel = Field(
+ default_factory=PhotoshopImageIOModel,
+ title="OCIO config"
+ )
+
+ create: PhotoshopCreatorPlugins = Field(
+ default_factory=PhotoshopCreatorPlugins,
+ title="Creator plugins"
+ )
+
+ publish: PhotoshopPublishPlugins = Field(
+ default_factory=PhotoshopPublishPlugins,
+ title="Publish plugins"
+ )
+
+ workfile_builder: WorkfileBuilderPlugin = Field(
+ default_factory=WorkfileBuilderPlugin,
+ title="Workfile Builder"
+ )
+
+
+DEFAULT_PHOTOSHOP_SETTING = {
+ "create": DEFAULT_CREATE_SETTINGS,
+ "publish": DEFAULT_PUBLISH_SETTINGS,
+ "workfile_builder": {
+ "create_first_version": False,
+ "custom_templates": []
+ }
+}
diff --git a/server_addon/photoshop/server/settings/publish_plugins.py b/server_addon/photoshop/server/settings/publish_plugins.py
new file mode 100644
index 0000000000..6bc72b4072
--- /dev/null
+++ b/server_addon/photoshop/server/settings/publish_plugins.py
@@ -0,0 +1,221 @@
+from pydantic import Field
+
+from ayon_server.settings import BaseSettingsModel
+
+
+create_flatten_image_enum = [
+ {"value": "flatten_with_images", "label": "Flatten with images"},
+ {"value": "flatten_only", "label": "Flatten only"},
+ {"value": "no", "label": "No"},
+]
+
+
+color_code_enum = [
+ {"value": "red", "label": "Red"},
+ {"value": "orange", "label": "Orange"},
+ {"value": "yellowColor", "label": "Yellow"},
+ {"value": "grain", "label": "Green"},
+ {"value": "blue", "label": "Blue"},
+ {"value": "violet", "label": "Violet"},
+ {"value": "gray", "label": "Gray"},
+]
+
+
+class ColorCodeMappings(BaseSettingsModel):
+ color_code: list[str] = Field(
+ title="Color codes for layers",
+ default_factory=list,
+ enum_resolver=lambda: color_code_enum,
+ )
+
+ layer_name_regex: list[str] = Field(
+ "",
+ title="Layer name regex"
+ )
+
+ product_type: str = Field(
+ "",
+ title="Resulting product type"
+ )
+
+ product_name_template: str = Field(
+ "",
+ title="Product name template"
+ )
+
+
+class ExtractedOptions(BaseSettingsModel):
+ tags: list[str] = Field(
+ title="Tags",
+ default_factory=list
+ )
+
+
+class CollectColorCodedInstancesPlugin(BaseSettingsModel):
+ """Set color for publishable layers, set its resulting product type
+ and template for product name. \n Can create flatten image from published
+ instances.
+ (Applicable only for remote publishing!)"""
+
+ enabled: bool = Field(True, title="Enabled")
+ create_flatten_image: str = Field(
+ "",
+ title="Create flatten image",
+ enum_resolver=lambda: create_flatten_image_enum,
+ )
+
+ flatten_product_type_template: str = Field(
+ "",
+ title="Subset template for flatten image"
+ )
+
+ color_code_mapping: list[ColorCodeMappings] = Field(
+ title="Color code mappings",
+ default_factory=ColorCodeMappings,
+ )
+
+
+class CollectReviewPlugin(BaseSettingsModel):
+ """Should review product be created"""
+ enabled: bool = Field(True, title="Enabled")
+
+
+class CollectVersionPlugin(BaseSettingsModel):
+ """Synchronize version for image and review instances by workfile version""" # noqa
+ enabled: bool = Field(True, title="Enabled")
+
+
+class ValidateContainersPlugin(BaseSettingsModel):
+ """Check that workfile contains latest version of loaded items""" # noqa
+ _isGroup = True
+ enabled: bool = True
+ optional: bool = Field(False, title="Optional")
+ active: bool = Field(True, title="Active")
+
+
+class ValidateNamingPlugin(BaseSettingsModel):
+ """Validate naming of products and layers""" # noqa
+ invalid_chars: str = Field(
+ '',
+ title="Regex pattern of invalid characters"
+ )
+
+ replace_char: str = Field(
+ '',
+ title="Replacement character"
+ )
+
+
+class ExtractImagePlugin(BaseSettingsModel):
+ """Currently only jpg and png are supported"""
+ formats: list[str] = Field(
+ title="Extract Formats",
+ default_factory=list,
+ )
+
+
+class ExtractReviewPlugin(BaseSettingsModel):
+ make_image_sequence: bool = Field(
+ False,
+ title="Make an image sequence instead of flatten image"
+ )
+
+ max_downscale_size: int = Field(
+ 8192,
+ title="Maximum size of sources for review",
+ description="FFMpeg can only handle limited resolution for creation of review and/or thumbnail", # noqa
+ gt=300, # greater than
+ le=16384, # less or equal
+ )
+
+ jpg_options: ExtractedOptions = Field(
+ title="Extracted jpg Options",
+ default_factory=ExtractedOptions
+ )
+
+ mov_options: ExtractedOptions = Field(
+ title="Extracted mov Options",
+ default_factory=ExtractedOptions
+ )
+
+
+class PhotoshopPublishPlugins(BaseSettingsModel):
+ CollectColorCodedInstances: CollectColorCodedInstancesPlugin = Field(
+ title="Collect Color Coded Instances",
+ default_factory=CollectColorCodedInstancesPlugin,
+ )
+ CollectReview: CollectReviewPlugin = Field(
+ title="Collect Review",
+ default_factory=CollectReviewPlugin,
+ )
+
+ CollectVersion: CollectVersionPlugin = Field(
+ title="Create Image",
+ default_factory=CollectVersionPlugin,
+ )
+
+ ValidateContainers: ValidateContainersPlugin = Field(
+ title="Validate Containers",
+ default_factory=ValidateContainersPlugin,
+ )
+
+ ValidateNaming: ValidateNamingPlugin = Field(
+ title="Validate naming of products and layers",
+ default_factory=ValidateNamingPlugin,
+ )
+
+ ExtractImage: ExtractImagePlugin = Field(
+ title="Extract Image",
+ default_factory=ExtractImagePlugin,
+ )
+
+ ExtractReview: ExtractReviewPlugin = Field(
+ title="Extract Review",
+ default_factory=ExtractReviewPlugin,
+ )
+
+
+DEFAULT_PUBLISH_SETTINGS = {
+ "CollectColorCodedInstances": {
+ "create_flatten_image": "no",
+ "flatten_product_type_template": "",
+ "color_code_mapping": []
+ },
+ "CollectReview": {
+ "enabled": True
+ },
+ "CollectVersion": {
+ "enabled": False
+ },
+ "ValidateContainers": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateNaming": {
+ "invalid_chars": "[ \\\\/+\\*\\?\\(\\)\\[\\]\\{\\}:,;]",
+ "replace_char": "_"
+ },
+ "ExtractImage": {
+ "formats": [
+ "png",
+ "jpg"
+ ]
+ },
+ "ExtractReview": {
+ "make_image_sequence": False,
+ "max_downscale_size": 8192,
+ "jpg_options": {
+ "tags": [
+ "review",
+ "ftrackreview"
+ ]
+ },
+ "mov_options": {
+ "tags": [
+ "review",
+ "ftrackreview"
+ ]
+ }
+ }
+}
diff --git a/server_addon/photoshop/server/settings/workfile_builder.py b/server_addon/photoshop/server/settings/workfile_builder.py
new file mode 100644
index 0000000000..ec2ee136ad
--- /dev/null
+++ b/server_addon/photoshop/server/settings/workfile_builder.py
@@ -0,0 +1,41 @@
+from pydantic import Field
+from pathlib import Path
+
+from ayon_server.settings import BaseSettingsModel
+
+
+class PathsTemplate(BaseSettingsModel):
+ windows: Path = Field(
+ '',
+ title="Windows"
+ )
+ darwin: Path = Field(
+ '',
+ title="MacOS"
+ )
+ linux: Path = Field(
+ '',
+ title="Linux"
+ )
+
+
+class CustomBuilderTemplate(BaseSettingsModel):
+ task_types: list[str] = Field(
+ default_factory=list,
+ title="Task types",
+ )
+ template_path: PathsTemplate = Field(
+ default_factory=PathsTemplate
+ )
+
+
+class WorkfileBuilderPlugin(BaseSettingsModel):
+ _title = "Workfile Builder"
+ create_first_version: bool = Field(
+ False,
+ title="Create first workfile"
+ )
+
+ custom_templates: list[CustomBuilderTemplate] = Field(
+ default_factory=CustomBuilderTemplate
+ )
diff --git a/server_addon/photoshop/server/version.py b/server_addon/photoshop/server/version.py
new file mode 100644
index 0000000000..d4b9e2d7f3
--- /dev/null
+++ b/server_addon/photoshop/server/version.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+"""Package declaring addon version."""
+__version__ = "0.1.0"
diff --git a/server_addon/resolve/server/__init__.py b/server_addon/resolve/server/__init__.py
new file mode 100644
index 0000000000..a84180d0f5
--- /dev/null
+++ b/server_addon/resolve/server/__init__.py
@@ -0,0 +1,19 @@
+from typing import Type
+
+from ayon_server.addons import BaseServerAddon
+
+from .version import __version__
+from .settings import ResolveSettings, DEFAULT_VALUES
+
+
+class ResolveAddon(BaseServerAddon):
+ name = "resolve"
+ title = "DaVinci Resolve"
+ version = __version__
+ settings_model: Type[ResolveSettings] = ResolveSettings
+ frontend_scopes = {}
+ services = {}
+
+ async def get_default_settings(self):
+ settings_model_cls = self.get_settings_model()
+ return settings_model_cls(**DEFAULT_VALUES)
diff --git a/server_addon/resolve/server/imageio.py b/server_addon/resolve/server/imageio.py
new file mode 100644
index 0000000000..c2bfcd40d0
--- /dev/null
+++ b/server_addon/resolve/server/imageio.py
@@ -0,0 +1,64 @@
+from pydantic import Field, validator
+from ayon_server.settings import BaseSettingsModel
+from ayon_server.settings.validators import ensure_unique_names
+
+
+class ImageIOConfigModel(BaseSettingsModel):
+ override_global_config: bool = Field(
+ False,
+ title="Override global OCIO config"
+ )
+ filepath: list[str] = Field(
+ default_factory=list,
+ title="Config path"
+ )
+
+
+class ImageIOFileRuleModel(BaseSettingsModel):
+ name: str = Field("", title="Rule name")
+ pattern: str = Field("", title="Regex pattern")
+ colorspace: str = Field("", title="Colorspace name")
+ ext: str = Field("", title="File extension")
+
+
+class ImageIOFileRulesModel(BaseSettingsModel):
+ activate_host_rules: bool = Field(False)
+ rules: list[ImageIOFileRuleModel] = Field(
+ default_factory=list,
+ title="Rules"
+ )
+
+ @validator("rules")
+ def validate_unique_outputs(cls, value):
+ ensure_unique_names(value)
+ return value
+
+
+class ImageIORemappingRulesModel(BaseSettingsModel):
+ host_native_name: str = Field(
+ title="Application native colorspace name"
+ )
+ ocio_name: str = Field(title="OCIO colorspace name")
+
+
+class ImageIORemappingModel(BaseSettingsModel):
+ rules: list[ImageIORemappingRulesModel] = Field(
+ default_factory=list)
+
+
+class ResolveImageIOModel(BaseSettingsModel):
+ activate_host_color_management: bool = Field(
+ True, title="Enable Color Management"
+ )
+ remapping: ImageIORemappingModel = Field(
+ title="Remapping colorspace names",
+ default_factory=ImageIORemappingModel
+ )
+ ocio_config: ImageIOConfigModel = Field(
+ default_factory=ImageIOConfigModel,
+ title="OCIO config"
+ )
+ file_rules: ImageIOFileRulesModel = Field(
+ default_factory=ImageIOFileRulesModel,
+ title="File Rules"
+ )
diff --git a/server_addon/resolve/server/settings.py b/server_addon/resolve/server/settings.py
new file mode 100644
index 0000000000..326f6bea1e
--- /dev/null
+++ b/server_addon/resolve/server/settings.py
@@ -0,0 +1,114 @@
+from pydantic import Field
+from ayon_server.settings import BaseSettingsModel
+
+from .imageio import ResolveImageIOModel
+
+
+class CreateShotClipModels(BaseSettingsModel):
+ hierarchy: str = Field(
+ "{folder}/{sequence}",
+ title="Shot parent hierarchy",
+ section="Shot Hierarchy And Rename Settings"
+ )
+ clipRename: bool = Field(
+ True,
+ title="Rename clips"
+ )
+ clipName: str = Field(
+ "{track}{sequence}{shot}",
+ title="Clip name template"
+ )
+ countFrom: int = Field(
+ 10,
+ title="Count sequence from"
+ )
+ countSteps: int = Field(
+ 10,
+ title="Stepping number"
+ )
+
+ folder: str = Field(
+ "shots",
+ title="{folder}",
+ section="Shot Template Keywords"
+ )
+ episode: str = Field(
+ "ep01",
+ title="{episode}"
+ )
+ sequence: str = Field(
+ "sq01",
+ title="{sequence}"
+ )
+ track: str = Field(
+ "{_track_}",
+ title="{track}"
+ )
+ shot: str = Field(
+ "sh###",
+ title="{shot}"
+ )
+
+ vSyncOn: bool = Field(
+ False,
+ title="Enable Vertical Sync",
+ section="Vertical Synchronization Of Attributes"
+ )
+
+ workfileFrameStart: int = Field(
+ 1001,
+ title="Workfiles Start Frame",
+ section="Shot Attributes"
+ )
+ handleStart: int = Field(
+ 10,
+ title="Handle start (head)"
+ )
+ handleEnd: int = Field(
+ 10,
+ title="Handle end (tail)"
+ )
+
+
+class CreatorPuginsModel(BaseSettingsModel):
+ CreateShotClip: CreateShotClipModels = Field(
+ default_factory=CreateShotClipModels,
+ title="Create Shot Clip"
+ )
+
+
+class ResolveSettings(BaseSettingsModel):
+ launch_openpype_menu_on_start: bool = Field(
+ False, title="Launch OpenPype menu on start of Resolve"
+ )
+ imageio: ResolveImageIOModel = Field(
+ default_factory=ResolveImageIOModel,
+ title="Color Management (ImageIO)"
+ )
+ create: CreatorPuginsModel = Field(
+ default_factory=CreatorPuginsModel,
+ title="Creator plugins",
+ )
+
+
+DEFAULT_VALUES = {
+ "launch_openpype_menu_on_start": False,
+ "create": {
+ "CreateShotClip": {
+ "hierarchy": "{folder}/{sequence}",
+ "clipRename": True,
+ "clipName": "{track}{sequence}{shot}",
+ "countFrom": 10,
+ "countSteps": 10,
+ "folder": "shots",
+ "episode": "ep01",
+ "sequence": "sq01",
+ "track": "{_track_}",
+ "shot": "sh###",
+ "vSyncOn": False,
+ "workfileFrameStart": 1001,
+ "handleStart": 10,
+ "handleEnd": 10
+ }
+ }
+}
diff --git a/server_addon/resolve/server/version.py b/server_addon/resolve/server/version.py
new file mode 100644
index 0000000000..3dc1f76bc6
--- /dev/null
+++ b/server_addon/resolve/server/version.py
@@ -0,0 +1 @@
+__version__ = "0.1.0"
diff --git a/server_addon/royal_render/server/__init__.py b/server_addon/royal_render/server/__init__.py
new file mode 100644
index 0000000000..c5f0aafa00
--- /dev/null
+++ b/server_addon/royal_render/server/__init__.py
@@ -0,0 +1,17 @@
+from typing import Type
+
+from ayon_server.addons import BaseServerAddon
+
+from .version import __version__
+from .settings import RoyalRenderSettings, DEFAULT_VALUES
+
+
+class RoyalRenderAddon(BaseServerAddon):
+ name = "royalrender"
+ version = __version__
+ title = "Royal Render"
+ settings_model: Type[RoyalRenderSettings] = RoyalRenderSettings
+
+ async def get_default_settings(self):
+ settings_model_cls = self.get_settings_model()
+ return settings_model_cls(**DEFAULT_VALUES)
diff --git a/server_addon/royal_render/server/settings.py b/server_addon/royal_render/server/settings.py
new file mode 100644
index 0000000000..677d7e2671
--- /dev/null
+++ b/server_addon/royal_render/server/settings.py
@@ -0,0 +1,70 @@
+from pydantic import Field
+from ayon_server.settings import BaseSettingsModel, MultiplatformPathModel
+
+
+class CustomPath(MultiplatformPathModel):
+ _layout = "expanded"
+
+
+class ServerListSubmodel(BaseSettingsModel):
+ _layout = "expanded"
+ name: str = Field("", title="Name")
+ value: CustomPath = Field(
+ default_factory=CustomPath
+ )
+
+
+class CollectSequencesFromJobModel(BaseSettingsModel):
+ review: bool = Field(True, title="Generate reviews from sequences")
+
+
+class PublishPluginsModel(BaseSettingsModel):
+ CollectSequencesFromJob: CollectSequencesFromJobModel = Field(
+ default_factory=CollectSequencesFromJobModel,
+ title="Collect Sequences from the Job"
+ )
+
+
+class RoyalRenderSettings(BaseSettingsModel):
+ enabled: bool = True
+ # WARNING/TODO this needs change
+ # - both system and project settings contained 'rr_path'
+ # where project settings did choose one of rr_path from system settings
+ # that is not possible in AYON
+ rr_paths: list[ServerListSubmodel] = Field(
+ default_factory=list,
+ title="Royal Render Root Paths",
+ scope=["studio"],
+ )
+ # This was 'rr_paths' in project settings and should be enum of
+ # 'rr_paths' from system settings, but that's not possible in AYON
+ selected_rr_paths: list[str] = Field(
+ default_factory=list,
+ title="Selected Royal Render Paths",
+ section="---",
+ )
+ publish: PublishPluginsModel = Field(
+ default_factory=PublishPluginsModel,
+ title="Publish plugins",
+ )
+
+
+DEFAULT_VALUES = {
+ "enabled": False,
+ "rr_paths": [
+ {
+ "name": "default",
+ "value": {
+ "windows": "",
+ "darwin": "",
+ "linux": ""
+ }
+ }
+ ],
+ "selected_rr_paths": ["default"],
+ "publish": {
+ "CollectSequencesFromJob": {
+ "review": True
+ }
+ }
+}
diff --git a/server_addon/royal_render/server/version.py b/server_addon/royal_render/server/version.py
new file mode 100644
index 0000000000..485f44ac21
--- /dev/null
+++ b/server_addon/royal_render/server/version.py
@@ -0,0 +1 @@
+__version__ = "0.1.1"
diff --git a/server_addon/timers_manager/server/__init__.py b/server_addon/timers_manager/server/__init__.py
new file mode 100644
index 0000000000..29f9d47370
--- /dev/null
+++ b/server_addon/timers_manager/server/__init__.py
@@ -0,0 +1,13 @@
+from typing import Type
+
+from ayon_server.addons import BaseServerAddon
+
+from .version import __version__
+from .settings import TimersManagerSettings
+
+
+class TimersManagerAddon(BaseServerAddon):
+ name = "timers_manager"
+ version = __version__
+ title = "Timers Manager"
+ settings_model: Type[TimersManagerSettings] = TimersManagerSettings
diff --git a/server_addon/timers_manager/server/settings.py b/server_addon/timers_manager/server/settings.py
new file mode 100644
index 0000000000..a5c5721a57
--- /dev/null
+++ b/server_addon/timers_manager/server/settings.py
@@ -0,0 +1,25 @@
+from pydantic import Field
+from ayon_server.settings import BaseSettingsModel
+
+
+class TimersManagerSettings(BaseSettingsModel):
+ auto_stop: bool = Field(
+ True,
+ title="Auto stop timer",
+ scope=["studio"],
+ )
+ full_time: int = Field(
+ 15,
+ title="Max idle time",
+ scope=["studio"],
+ )
+ message_time: float = Field(
+ 0.5,
+ title="When dialog will show",
+ scope=["studio"],
+ )
+ disregard_publishing: bool = Field(
+ False,
+ title="Disregard publishing",
+ scope=["studio"],
+ )
diff --git a/server_addon/timers_manager/server/version.py b/server_addon/timers_manager/server/version.py
new file mode 100644
index 0000000000..485f44ac21
--- /dev/null
+++ b/server_addon/timers_manager/server/version.py
@@ -0,0 +1 @@
+__version__ = "0.1.1"
diff --git a/server_addon/traypublisher/server/LICENSE b/server_addon/traypublisher/server/LICENSE
new file mode 100644
index 0000000000..d645695673
--- /dev/null
+++ b/server_addon/traypublisher/server/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/server_addon/traypublisher/server/README.md b/server_addon/traypublisher/server/README.md
new file mode 100644
index 0000000000..c0029bc782
--- /dev/null
+++ b/server_addon/traypublisher/server/README.md
@@ -0,0 +1,4 @@
+Photoshp Addon
+===============
+
+Integration with Adobe Traypublisher.
diff --git a/server_addon/traypublisher/server/__init__.py b/server_addon/traypublisher/server/__init__.py
new file mode 100644
index 0000000000..e6f079609f
--- /dev/null
+++ b/server_addon/traypublisher/server/__init__.py
@@ -0,0 +1,16 @@
+from ayon_server.addons import BaseServerAddon
+
+from .version import __version__
+from .settings import TraypublisherSettings, DEFAULT_TRAYPUBLISHER_SETTING
+
+
+class Traypublisher(BaseServerAddon):
+ name = "traypublisher"
+ title = "TrayPublisher"
+ version = __version__
+
+ settings_model = TraypublisherSettings
+
+ async def get_default_settings(self):
+ settings_model_cls = self.get_settings_model()
+ return settings_model_cls(**DEFAULT_TRAYPUBLISHER_SETTING)
diff --git a/server_addon/traypublisher/server/settings/__init__.py b/server_addon/traypublisher/server/settings/__init__.py
new file mode 100644
index 0000000000..bcf8beffa7
--- /dev/null
+++ b/server_addon/traypublisher/server/settings/__init__.py
@@ -0,0 +1,10 @@
+from .main import (
+ TraypublisherSettings,
+ DEFAULT_TRAYPUBLISHER_SETTING,
+)
+
+
+__all__ = (
+ "TraypublisherSettings",
+ "DEFAULT_TRAYPUBLISHER_SETTING",
+)
diff --git a/server_addon/traypublisher/server/settings/creator_plugins.py b/server_addon/traypublisher/server/settings/creator_plugins.py
new file mode 100644
index 0000000000..345cb92e63
--- /dev/null
+++ b/server_addon/traypublisher/server/settings/creator_plugins.py
@@ -0,0 +1,46 @@
+from pydantic import Field
+
+from ayon_server.settings import BaseSettingsModel
+
+
+class BatchMovieCreatorPlugin(BaseSettingsModel):
+ """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')"""
+
+ default_variants: list[str] = Field(
+ title="Default variants",
+ default_factory=list
+ )
+
+ default_tasks: list[str] = Field(
+ title="Default tasks",
+ default_factory=list
+ )
+
+ extensions: list[str] = Field(
+ title="Extensions",
+ default_factory=list
+ )
+
+
+class TrayPublisherCreatePluginsModel(BaseSettingsModel):
+ BatchMovieCreator: BatchMovieCreatorPlugin = Field(
+ title="Batch Movie Creator",
+ default_factory=BatchMovieCreatorPlugin
+ )
+
+
+DEFAULT_CREATORS = {
+ "BatchMovieCreator": {
+ "default_variants": [
+ "Main"
+ ],
+ "default_tasks": [
+ "Compositing"
+ ],
+ "extensions": [
+ ".mov"
+ ]
+ },
+}
diff --git a/server_addon/traypublisher/server/settings/editorial_creators.py b/server_addon/traypublisher/server/settings/editorial_creators.py
new file mode 100644
index 0000000000..4111f22576
--- /dev/null
+++ b/server_addon/traypublisher/server/settings/editorial_creators.py
@@ -0,0 +1,181 @@
+from pydantic import Field
+
+from ayon_server.settings import BaseSettingsModel, task_types_enum
+
+
+class ClipNameTokenizerItem(BaseSettingsModel):
+ _layout = "expanded"
+ # TODO was 'dict-modifiable', is list of dicts now, must be fixed in code
+ name: str = Field("#TODO", title="Tokenizer name")
+ regex: str = Field("", title="Tokenizer regex")
+
+
+class ShotAddTasksItem(BaseSettingsModel):
+ _layout = "expanded"
+ # TODO was 'dict-modifiable', is list of dicts now, must be fixed in code
+ name: str = Field('', title="Key")
+ task_type: list[str] = Field(
+ title="Task type",
+ default_factory=list,
+ enum_resolver=task_types_enum)
+
+
+class ShotRenameSubmodel(BaseSettingsModel):
+ enabled: bool = True
+ shot_rename_template: str = Field(
+ "",
+ title="Shot rename template"
+ )
+
+
+parent_type_enum = [
+ {"value": "Project", "label": "Project"},
+ {"value": "Folder", "label": "Folder"},
+ {"value": "Episode", "label": "Episode"},
+ {"value": "Sequence", "label": "Sequence"},
+]
+
+
+class TokenToParentConvertorItem(BaseSettingsModel):
+ # TODO - was 'type' must be renamed in code to `parent_type`
+ parent_type: str = Field(
+ "Project",
+ enum_resolver=lambda: parent_type_enum
+ )
+ name: str = Field(
+ "",
+ title="Parent token name",
+ description="Unique name used in `Parent path template`"
+ )
+ value: str = Field(
+ "",
+ title="Parent token value",
+ description="Template where any text, Anatomy keys and Tokens could be used" # noqa
+ )
+
+
+class ShotHierchySubmodel(BaseSettingsModel):
+ enabled: bool = True
+ parents_path: str = Field(
+ "",
+ title="Parents path template",
+ description="Using keys from \"Token to parent convertor\" or tokens directly" # noqa
+ )
+ parents: list[TokenToParentConvertorItem] = Field(
+ default_factory=TokenToParentConvertorItem,
+ title="Token to parent convertor"
+ )
+
+
+output_file_type = [
+ {"value": ".mp4", "label": "MP4"},
+ {"value": ".mov", "label": "MOV"},
+ {"value": ".wav", "label": "WAV"}
+]
+
+
+class ProductTypePresetItem(BaseSettingsModel):
+ product_type: str = Field("", title="Product type")
+ # TODO add placeholder '< Inherited >'
+ variant: str = Field("", title="Variant")
+ review: bool = Field(True, title="Review")
+ output_file_type: str = Field(
+ ".mp4",
+ enum_resolver=lambda: output_file_type
+ )
+
+
+class EditorialSimpleCreatorPlugin(BaseSettingsModel):
+ default_variants: list[str] = Field(
+ default_factory=list,
+ title="Default Variants"
+ )
+ clip_name_tokenizer: list[ClipNameTokenizerItem] = Field(
+ default_factory=ClipNameTokenizerItem,
+ description=(
+ "Using Regex expression to create tokens. \nThose can be used"
+ " later in \"Shot rename\" creator \nor \"Shot hierarchy\"."
+ "\n\nTokens should be decorated with \"_\" on each side"
+ )
+ )
+ shot_rename: ShotRenameSubmodel = Field(
+ title="Shot Rename",
+ default_factory=ShotRenameSubmodel
+ )
+ shot_hierarchy: ShotHierchySubmodel = Field(
+ title="Shot Hierarchy",
+ default_factory=ShotHierchySubmodel
+ )
+ shot_add_tasks: list[ShotAddTasksItem] = Field(
+ title="Add tasks to shot",
+ default_factory=ShotAddTasksItem
+ )
+ product_type_presets: list[ProductTypePresetItem] = Field(
+ default_factory=list
+ )
+
+
+class TraypublisherEditorialCreatorPlugins(BaseSettingsModel):
+ editorial_simple: EditorialSimpleCreatorPlugin = Field(
+ title="Editorial simple creator",
+ default_factory=EditorialSimpleCreatorPlugin,
+ )
+
+
+DEFAULT_EDITORIAL_CREATORS = {
+ "editorial_simple": {
+ "default_variants": [
+ "Main"
+ ],
+ "clip_name_tokenizer": [
+ {"name": "_sequence_", "regex": "(sc\\d{3})"},
+ {"name": "_shot_", "regex": "(sh\\d{3})"}
+ ],
+ "shot_rename": {
+ "enabled": True,
+ "shot_rename_template": "{project[code]}_{_sequence_}_{_shot_}"
+ },
+ "shot_hierarchy": {
+ "enabled": True,
+ "parents_path": "{project}/{folder}/{sequence}",
+ "parents": [
+ {
+ "parent_type": "Project",
+ "name": "project",
+ "value": "{project[name]}"
+ },
+ {
+ "parent_type": "Folder",
+ "name": "folder",
+ "value": "shots"
+ },
+ {
+ "parent_type": "Sequence",
+ "name": "sequence",
+ "value": "{_sequence_}"
+ }
+ ]
+ },
+ "shot_add_tasks": [],
+ "product_type_presets": [
+ {
+ "product_type": "review",
+ "variant": "Reference",
+ "review": True,
+ "output_file_type": ".mp4"
+ },
+ {
+ "product_type": "plate",
+ "variant": "",
+ "review": False,
+ "output_file_type": ".mov"
+ },
+ {
+ "product_type": "audio",
+ "variant": "",
+ "review": False,
+ "output_file_type": ".wav"
+ }
+ ]
+ }
+}
diff --git a/server_addon/traypublisher/server/settings/imageio.py b/server_addon/traypublisher/server/settings/imageio.py
new file mode 100644
index 0000000000..3df0d2f2fb
--- /dev/null
+++ b/server_addon/traypublisher/server/settings/imageio.py
@@ -0,0 +1,48 @@
+from pydantic import Field, validator
+from ayon_server.settings import BaseSettingsModel
+from ayon_server.settings.validators import ensure_unique_names
+
+
+class ImageIOConfigModel(BaseSettingsModel):
+ override_global_config: bool = Field(
+ False,
+ title="Override global OCIO config"
+ )
+ filepath: list[str] = Field(
+ default_factory=list,
+ title="Config path"
+ )
+
+
+class ImageIOFileRuleModel(BaseSettingsModel):
+ name: str = Field("", title="Rule name")
+ pattern: str = Field("", title="Regex pattern")
+ colorspace: str = Field("", title="Colorspace name")
+ ext: str = Field("", title="File extension")
+
+
+class ImageIOFileRulesModel(BaseSettingsModel):
+ activate_host_rules: bool = Field(False)
+ rules: list[ImageIOFileRuleModel] = Field(
+ default_factory=list,
+ title="Rules"
+ )
+
+ @validator("rules")
+ def validate_unique_outputs(cls, value):
+ ensure_unique_names(value)
+ return value
+
+
+class TrayPublisherImageIOModel(BaseSettingsModel):
+ activate_host_color_management: bool = Field(
+ True, title="Enable Color Management"
+ )
+ ocio_config: ImageIOConfigModel = Field(
+ default_factory=ImageIOConfigModel,
+ title="OCIO config"
+ )
+ file_rules: ImageIOFileRulesModel = Field(
+ default_factory=ImageIOFileRulesModel,
+ title="File Rules"
+ )
diff --git a/server_addon/traypublisher/server/settings/main.py b/server_addon/traypublisher/server/settings/main.py
new file mode 100644
index 0000000000..fad96bef2f
--- /dev/null
+++ b/server_addon/traypublisher/server/settings/main.py
@@ -0,0 +1,52 @@
+from pydantic import Field
+from ayon_server.settings import BaseSettingsModel
+
+from .imageio import TrayPublisherImageIOModel
+from .simple_creators import (
+ SimpleCreatorPlugin,
+ DEFAULT_SIMPLE_CREATORS,
+)
+from .editorial_creators import (
+ TraypublisherEditorialCreatorPlugins,
+ DEFAULT_EDITORIAL_CREATORS,
+)
+from .creator_plugins import (
+ TrayPublisherCreatePluginsModel,
+ DEFAULT_CREATORS,
+)
+from .publish_plugins import (
+ TrayPublisherPublishPlugins,
+ DEFAULT_PUBLISH_PLUGINS,
+)
+
+
+class TraypublisherSettings(BaseSettingsModel):
+ """Traypublisher Project Settings."""
+ imageio: TrayPublisherImageIOModel = Field(
+ default_factory=TrayPublisherImageIOModel,
+ title="Color Management (ImageIO)"
+ )
+ simple_creators: list[SimpleCreatorPlugin] = Field(
+ title="Simple Create Plugins",
+ default_factory=SimpleCreatorPlugin,
+ )
+ editorial_creators: TraypublisherEditorialCreatorPlugins = Field(
+ title="Editorial Creators",
+ default_factory=TraypublisherEditorialCreatorPlugins,
+ )
+ create: TrayPublisherCreatePluginsModel = Field(
+ title="Create",
+ default_factory=TrayPublisherCreatePluginsModel
+ )
+ publish: TrayPublisherPublishPlugins = Field(
+ title="Publish Plugins",
+ default_factory=TrayPublisherPublishPlugins
+ )
+
+
+DEFAULT_TRAYPUBLISHER_SETTING = {
+ "simple_creators": DEFAULT_SIMPLE_CREATORS,
+ "editorial_creators": DEFAULT_EDITORIAL_CREATORS,
+ "create": DEFAULT_CREATORS,
+ "publish": DEFAULT_PUBLISH_PLUGINS,
+}
diff --git a/server_addon/traypublisher/server/settings/publish_plugins.py b/server_addon/traypublisher/server/settings/publish_plugins.py
new file mode 100644
index 0000000000..8c844f29f2
--- /dev/null
+++ b/server_addon/traypublisher/server/settings/publish_plugins.py
@@ -0,0 +1,50 @@
+from pydantic import Field
+
+from ayon_server.settings import BaseSettingsModel
+
+
+class ValidatePluginModel(BaseSettingsModel):
+ _isGroup = True
+ enabled: bool = True
+ optional: bool = Field(True, title="Optional")
+ active: bool = Field(True, title="Active")
+
+
+class ValidateFrameRangeModel(ValidatePluginModel):
+ """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')"""
+
+
+class TrayPublisherPublishPlugins(BaseSettingsModel):
+ CollectFrameDataFromAssetEntity: ValidatePluginModel = Field(
+ default_factory=ValidatePluginModel,
+ title="Collect Frame Data From Folder Entity",
+ )
+ ValidateFrameRange: ValidateFrameRangeModel = Field(
+ title="Validate Frame Range",
+ default_factory=ValidateFrameRangeModel,
+ )
+ ValidateExistingVersion: ValidatePluginModel = Field(
+ title="Validate Existing Version",
+ default_factory=ValidatePluginModel,
+ )
+
+
+DEFAULT_PUBLISH_PLUGINS = {
+ "CollectFrameDataFromAssetEntity": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateFrameRange": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateExistingVersion": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ }
+}
diff --git a/server_addon/traypublisher/server/settings/simple_creators.py b/server_addon/traypublisher/server/settings/simple_creators.py
new file mode 100644
index 0000000000..94d6602738
--- /dev/null
+++ b/server_addon/traypublisher/server/settings/simple_creators.py
@@ -0,0 +1,292 @@
+from pydantic import Field
+
+from ayon_server.settings import BaseSettingsModel
+
+
+class SimpleCreatorPlugin(BaseSettingsModel):
+ _layout = "expanded"
+ product_type: str = Field("", title="Product type")
+ # TODO add placeholder
+ identifier: str = Field("", title="Identifier")
+ label: str = Field("", title="Label")
+ icon: str = Field("", title="Icon")
+ default_variants: list[str] = Field(
+ default_factory=list,
+ title="Default Variants"
+ )
+ description: str = Field(
+ "",
+ title="Description",
+ widget="textarea"
+ )
+ detailed_description: str = Field(
+ "",
+ title="Detailed Description",
+ widget="textarea"
+ )
+ allow_sequences: bool = Field(
+ False,
+ title="Allow sequences"
+ )
+ allow_multiple_items: bool = Field(
+ False,
+ title="Allow multiple items"
+ )
+ allow_version_control: bool = Field(
+ False,
+ title="Allow version control"
+ )
+ extensions: list[str] = Field(
+ default_factory=list,
+ title="Extensions"
+ )
+
+
+DEFAULT_SIMPLE_CREATORS = [
+ {
+ "product_type": "workfile",
+ "identifier": "",
+ "label": "Workfile",
+ "icon": "fa.file",
+ "default_variants": [
+ "Main"
+ ],
+ "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,
+ "allow_version_control": False,
+ "extensions": [
+ ".ma",
+ ".mb",
+ ".nk",
+ ".hrox",
+ ".hip",
+ ".hiplc",
+ ".hipnc",
+ ".blend",
+ ".scn",
+ ".tvpp",
+ ".comp",
+ ".zip",
+ ".prproj",
+ ".drp",
+ ".psd",
+ ".psb",
+ ".aep"
+ ]
+ },
+ {
+ "product_type": "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,
+ "allow_version_control": False,
+ "extensions": [
+ ".ma",
+ ".mb",
+ ".obj",
+ ".abc",
+ ".fbx",
+ ".bgeo",
+ ".bgeogz",
+ ".bgeosc",
+ ".usd",
+ ".blend"
+ ]
+ },
+ {
+ "product_type": "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,
+ "allow_version_control": False,
+ "extensions": [
+ ".abc",
+ ".bgeo",
+ ".bgeogz",
+ ".bgeosc"
+ ]
+ },
+ {
+ "product_type": "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,
+ "allow_version_control": False,
+ "extensions": [
+ ".exr",
+ ".png",
+ ".dpx",
+ ".jpg",
+ ".tiff",
+ ".tif",
+ ".mov",
+ ".mp4",
+ ".avi"
+ ]
+ },
+ {
+ "product_type": "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,
+ "allow_version_control": False,
+ "extensions": [
+ ".exr",
+ ".png",
+ ".dpx",
+ ".jpg",
+ ".jpeg",
+ ".tiff",
+ ".tif",
+ ".mov",
+ ".mp4",
+ ".avi"
+ ]
+ },
+ {
+ "product_type": "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,
+ "allow_version_control": False,
+ "extensions": [
+ ".abc",
+ ".ma",
+ ".hip",
+ ".blend",
+ ".fbx",
+ ".usd"
+ ]
+ },
+ {
+ "product_type": "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 product type. References, textures, concept art, matte paints. This is a fallback 2d product type for everything that doesn't fit more specific product type.",
+ "allow_sequences": False,
+ "allow_multiple_items": True,
+ "allow_version_control": False,
+ "extensions": [
+ ".exr",
+ ".jpg",
+ ".jpeg",
+ ".dpx",
+ ".bmp",
+ ".tif",
+ ".tiff",
+ ".png",
+ ".psb",
+ ".psd"
+ ]
+ },
+ {
+ "product_type": "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,
+ "allow_version_control": False,
+ "extensions": [
+ ".vdb"
+ ]
+ },
+ {
+ "product_type": "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,
+ "allow_version_control": False,
+ "extensions": []
+ },
+ {
+ "product_type": "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,
+ "allow_version_control": False,
+ "extensions": [
+ ".ma",
+ ".blend",
+ ".hip",
+ ".hda"
+ ]
+ },
+ {
+ "product_type": "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,
+ "allow_version_control": False,
+ "extensions": []
+ }
+]
diff --git a/server_addon/traypublisher/server/version.py b/server_addon/traypublisher/server/version.py
new file mode 100644
index 0000000000..df0c92f1e2
--- /dev/null
+++ b/server_addon/traypublisher/server/version.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+"""Package declaring addon version."""
+__version__ = "0.1.2"
diff --git a/server_addon/tvpaint/server/__init__.py b/server_addon/tvpaint/server/__init__.py
new file mode 100644
index 0000000000..033d7d3792
--- /dev/null
+++ b/server_addon/tvpaint/server/__init__.py
@@ -0,0 +1,17 @@
+from typing import Type
+
+from ayon_server.addons import BaseServerAddon
+
+from .version import __version__
+from .settings import TvpaintSettings, DEFAULT_VALUES
+
+
+class TvpaintAddon(BaseServerAddon):
+ name = "tvpaint"
+ title = "TVPaint"
+ version = __version__
+ settings_model: Type[TvpaintSettings] = TvpaintSettings
+
+ async def get_default_settings(self):
+ settings_model_cls = self.get_settings_model()
+ return settings_model_cls(**DEFAULT_VALUES)
diff --git a/server_addon/tvpaint/server/settings/__init__.py b/server_addon/tvpaint/server/settings/__init__.py
new file mode 100644
index 0000000000..abee32e897
--- /dev/null
+++ b/server_addon/tvpaint/server/settings/__init__.py
@@ -0,0 +1,10 @@
+from .main import (
+ TvpaintSettings,
+ DEFAULT_VALUES,
+)
+
+
+__all__ = (
+ "TvpaintSettings",
+ "DEFAULT_VALUES",
+)
diff --git a/server_addon/tvpaint/server/settings/create_plugins.py b/server_addon/tvpaint/server/settings/create_plugins.py
new file mode 100644
index 0000000000..349bfdd288
--- /dev/null
+++ b/server_addon/tvpaint/server/settings/create_plugins.py
@@ -0,0 +1,133 @@
+from pydantic import Field
+from ayon_server.settings import BaseSettingsModel
+
+
+class CreateWorkfileModel(BaseSettingsModel):
+ enabled: bool = Field(True)
+ default_variant: str = Field(title="Default variant")
+ default_variants: list[str] = Field(
+ default_factory=list, title="Default variants")
+
+
+class CreateReviewModel(BaseSettingsModel):
+ enabled: bool = Field(True)
+ active_on_create: bool = Field(True, title="Active by default")
+ default_variant: str = Field(title="Default variant")
+ default_variants: list[str] = Field(
+ default_factory=list, title="Default variants")
+
+
+class CreateRenderSceneModel(BaseSettingsModel):
+ enabled: bool = Field(True)
+ active_on_create: bool = Field(True, title="Active by default")
+ mark_for_review: bool = Field(True, title="Review by default")
+ default_pass_name: str = Field(title="Default beauty pass")
+ default_variant: str = Field(title="Default variant")
+ default_variants: list[str] = Field(
+ default_factory=list, title="Default variants")
+
+
+class CreateRenderLayerModel(BaseSettingsModel):
+ mark_for_review: bool = Field(True, title="Review by default")
+ default_pass_name: str = Field(title="Default beauty pass")
+ default_variant: str = Field(title="Default variant")
+ default_variants: list[str] = Field(
+ default_factory=list, title="Default variants")
+
+
+class CreateRenderPassModel(BaseSettingsModel):
+ mark_for_review: bool = Field(True, title="Review by default")
+ default_variant: str = Field(title="Default variant")
+ default_variants: list[str] = Field(
+ default_factory=list, title="Default variants")
+
+
+class AutoDetectCreateRenderModel(BaseSettingsModel):
+ """The creator tries to auto-detect Render Layers and Render Passes in scene.
+
+ For Render Layers is used group name as a variant and for Render Passes is
+ used TVPaint layer name.
+
+ Group names can be renamed by their used order in scene. The renaming
+ template where can be used '{group_index}' formatting key which is
+ filled by "used position index of group".
+ - Template: 'L{group_index}'
+ - Group offset: '10'
+ - Group padding: '3'
+
+ Would create group names "L010", "L020", ...
+ """
+
+ enabled: bool = Field(True)
+ allow_group_rename: bool = Field(title="Allow group rename")
+ group_name_template: str = Field(title="Group name template")
+ group_idx_offset: int = Field(1, title="Group index Offset", ge=1)
+ group_idx_padding: int = Field(4, title="Group index Padding", ge=1)
+
+
+class CreatePluginsModel(BaseSettingsModel):
+ create_workfile: CreateWorkfileModel = Field(
+ default_factory=CreateWorkfileModel,
+ title="Create Workfile"
+ )
+ create_review: CreateReviewModel = Field(
+ default_factory=CreateReviewModel,
+ title="Create Review"
+ )
+ create_render_scene: CreateRenderSceneModel = Field(
+ default_factory=CreateReviewModel,
+ title="Create Render Scene"
+ )
+ create_render_layer: CreateRenderLayerModel= Field(
+ default_factory=CreateRenderLayerModel,
+ title="Create Render Layer"
+ )
+ create_render_pass: CreateRenderPassModel = Field(
+ default_factory=CreateRenderPassModel,
+ title="Create Render Pass"
+ )
+ auto_detect_render: AutoDetectCreateRenderModel = Field(
+ default_factory=AutoDetectCreateRenderModel,
+ title="Auto-Detect Create Render",
+ )
+
+
+DEFAULT_CREATE_SETTINGS = {
+ "create_workfile": {
+ "enabled": True,
+ "default_variant": "Main",
+ "default_variants": []
+ },
+ "create_review": {
+ "enabled": True,
+ "active_on_create": True,
+ "default_variant": "Main",
+ "default_variants": []
+ },
+ "create_render_scene": {
+ "enabled": True,
+ "active_on_create": False,
+ "mark_for_review": True,
+ "default_pass_name": "beauty",
+ "default_variant": "Main",
+ "default_variants": []
+ },
+ "create_render_layer": {
+ "mark_for_review": False,
+ "default_pass_name": "beauty",
+ "default_variant": "Main",
+ "default_variants": []
+ },
+ "create_render_pass": {
+ "mark_for_review": False,
+ "default_variant": "Main",
+ "default_variants": []
+ },
+ "auto_detect_render": {
+ "enabled": False,
+ "allow_group_rename": True,
+ "group_name_template": "L{group_index}",
+ "group_idx_offset": 10,
+ "group_idx_padding": 3
+ }
+}
diff --git a/server_addon/tvpaint/server/settings/filters.py b/server_addon/tvpaint/server/settings/filters.py
new file mode 100644
index 0000000000..009febae06
--- /dev/null
+++ b/server_addon/tvpaint/server/settings/filters.py
@@ -0,0 +1,19 @@
+from pydantic import Field
+
+from ayon_server.settings import BaseSettingsModel
+
+
+class FiltersSubmodel(BaseSettingsModel):
+ _layout = "compact"
+ name: str = Field(title="Name")
+ value: str = Field(
+ "",
+ title="Textarea",
+ widget="textarea",
+ )
+
+
+class PublishFiltersModel(BaseSettingsModel):
+ env_search_replace_values: list[FiltersSubmodel] = Field(
+ default_factory=list
+ )
diff --git a/server_addon/tvpaint/server/settings/imageio.py b/server_addon/tvpaint/server/settings/imageio.py
new file mode 100644
index 0000000000..50f8b7eef4
--- /dev/null
+++ b/server_addon/tvpaint/server/settings/imageio.py
@@ -0,0 +1,48 @@
+from pydantic import Field, validator
+from ayon_server.settings import BaseSettingsModel
+from ayon_server.settings.validators import ensure_unique_names
+
+
+class ImageIOConfigModel(BaseSettingsModel):
+ override_global_config: bool = Field(
+ False,
+ title="Override global OCIO config"
+ )
+ filepath: list[str] = Field(
+ default_factory=list,
+ title="Config path"
+ )
+
+
+class ImageIOFileRuleModel(BaseSettingsModel):
+ name: str = Field("", title="Rule name")
+ pattern: str = Field("", title="Regex pattern")
+ colorspace: str = Field("", title="Colorspace name")
+ ext: str = Field("", title="File extension")
+
+
+class ImageIOFileRulesModel(BaseSettingsModel):
+ activate_host_rules: bool = Field(False)
+ rules: list[ImageIOFileRuleModel] = Field(
+ default_factory=list,
+ title="Rules"
+ )
+
+ @validator("rules")
+ def validate_unique_outputs(cls, value):
+ ensure_unique_names(value)
+ return value
+
+
+class TVPaintImageIOModel(BaseSettingsModel):
+ activate_host_color_management: bool = Field(
+ True, title="Enable Color Management"
+ )
+ ocio_config: ImageIOConfigModel = Field(
+ default_factory=ImageIOConfigModel,
+ title="OCIO config"
+ )
+ file_rules: ImageIOFileRulesModel = Field(
+ default_factory=ImageIOFileRulesModel,
+ title="File Rules"
+ )
diff --git a/server_addon/tvpaint/server/settings/main.py b/server_addon/tvpaint/server/settings/main.py
new file mode 100644
index 0000000000..4cd6ac4b1a
--- /dev/null
+++ b/server_addon/tvpaint/server/settings/main.py
@@ -0,0 +1,90 @@
+from pydantic import Field, validator
+from ayon_server.settings import (
+ BaseSettingsModel,
+ ensure_unique_names,
+)
+
+from .imageio import TVPaintImageIOModel
+from .workfile_builder import WorkfileBuilderPlugin
+from .create_plugins import CreatePluginsModel, DEFAULT_CREATE_SETTINGS
+from .publish_plugins import (
+ PublishPluginsModel,
+ LoadPluginsModel,
+ DEFAULT_PUBLISH_SETTINGS,
+)
+
+
+class PublishGUIFilterItemModel(BaseSettingsModel):
+ _layout = "compact"
+ name: str = Field(title="Name")
+ value: bool = Field(True, title="Active")
+
+
+class PublishGUIFiltersModel(BaseSettingsModel):
+ _layout = "compact"
+ name: str = Field(title="Name")
+ value: list[PublishGUIFilterItemModel] = Field(default_factory=list)
+
+ @validator("value")
+ def validate_unique_outputs(cls, value):
+ ensure_unique_names(value)
+ return value
+
+
+class TvpaintSettings(BaseSettingsModel):
+ imageio: TVPaintImageIOModel = Field(
+ default_factory=TVPaintImageIOModel,
+ title="Color Management (ImageIO)"
+ )
+ stop_timer_on_application_exit: bool = Field(
+ title="Stop timer on application exit")
+ create: CreatePluginsModel = Field(
+ default_factory=CreatePluginsModel,
+ title="Create plugins"
+ )
+ publish: PublishPluginsModel = Field(
+ default_factory=PublishPluginsModel,
+ title="Publish plugins")
+ load: LoadPluginsModel = Field(
+ default_factory=LoadPluginsModel,
+ title="Load plugins")
+ workfile_builder: WorkfileBuilderPlugin = Field(
+ default_factory=WorkfileBuilderPlugin,
+ title="Workfile Builder"
+ )
+ filters: list[PublishGUIFiltersModel] = Field(
+ default_factory=list,
+ title="Publish GUI Filters")
+
+ @validator("filters")
+ def validate_unique_outputs(cls, value):
+ ensure_unique_names(value)
+ return value
+
+
+DEFAULT_VALUES = {
+ "stop_timer_on_application_exit": False,
+ "create": DEFAULT_CREATE_SETTINGS,
+ "publish": DEFAULT_PUBLISH_SETTINGS,
+ "load": {
+ "LoadImage": {
+ "defaults": {
+ "stretch": True,
+ "timestretch": True,
+ "preload": True
+ }
+ },
+ "ImportImage": {
+ "defaults": {
+ "stretch": True,
+ "timestretch": True,
+ "preload": True
+ }
+ }
+ },
+ "workfile_builder": {
+ "create_first_version": False,
+ "custom_templates": []
+ },
+ "filters": []
+}
diff --git a/server_addon/tvpaint/server/settings/publish_plugins.py b/server_addon/tvpaint/server/settings/publish_plugins.py
new file mode 100644
index 0000000000..76c7eaac01
--- /dev/null
+++ b/server_addon/tvpaint/server/settings/publish_plugins.py
@@ -0,0 +1,132 @@
+from pydantic import Field
+
+from ayon_server.settings import BaseSettingsModel
+from ayon_server.types import ColorRGBA_uint8
+
+
+class CollectRenderInstancesModel(BaseSettingsModel):
+ ignore_render_pass_transparency: bool = Field(
+ title="Ignore Render Pass opacity"
+ )
+
+
+class ExtractSequenceModel(BaseSettingsModel):
+ """Review BG color is used for whole scene review and for thumbnails."""
+ # TODO Use alpha color
+ review_bg: ColorRGBA_uint8 = Field(
+ (255, 255, 255, 1.0),
+ title="Review BG color")
+
+
+class ValidatePluginModel(BaseSettingsModel):
+ enabled: bool = True
+ optional: bool = Field(True, title="Optional")
+ active: bool = Field(True, title="Active")
+
+
+def compression_enum():
+ return [
+ {"value": "ZIP", "label": "ZIP"},
+ {"value": "ZIPS", "label": "ZIPS"},
+ {"value": "DWAA", "label": "DWAA"},
+ {"value": "DWAB", "label": "DWAB"},
+ {"value": "PIZ", "label": "PIZ"},
+ {"value": "RLE", "label": "RLE"},
+ {"value": "PXR24", "label": "PXR24"},
+ {"value": "B44", "label": "B44"},
+ {"value": "B44A", "label": "B44A"},
+ {"value": "none", "label": "None"}
+ ]
+
+
+class ExtractConvertToEXRModel(BaseSettingsModel):
+ """WARNING: This plugin does not work on MacOS (using OIIO tool)."""
+ enabled: bool = False
+ replace_pngs: bool = True
+
+ exr_compression: str = Field(
+ "ZIP",
+ enum_resolver=compression_enum,
+ title="EXR Compression"
+ )
+
+
+class LoadImageDefaultModel(BaseSettingsModel):
+ _layout = "expanded"
+ stretch: bool = Field(title="Stretch")
+ timestretch: bool = Field(title="TimeStretch")
+ preload: bool = Field(title="Preload")
+
+
+class LoadImageModel(BaseSettingsModel):
+ defaults: LoadImageDefaultModel = Field(
+ default_factory=LoadImageDefaultModel
+ )
+
+
+class PublishPluginsModel(BaseSettingsModel):
+ CollectRenderInstances: CollectRenderInstancesModel = Field(
+ default_factory=CollectRenderInstancesModel,
+ title="Collect Render Instances")
+ ExtractSequence: ExtractSequenceModel = Field(
+ default_factory=ExtractSequenceModel,
+ title="Extract Sequence")
+ ValidateProjectSettings: ValidatePluginModel = Field(
+ default_factory=ValidatePluginModel,
+ title="Validate Project Settings")
+ ValidateMarks: ValidatePluginModel = Field(
+ default_factory=ValidatePluginModel,
+ title="Validate MarkIn/Out")
+ ValidateStartFrame: ValidatePluginModel = Field(
+ default_factory=ValidatePluginModel,
+ title="Validate Scene Start Frame")
+ ValidateAssetName: ValidatePluginModel = Field(
+ default_factory=ValidatePluginModel,
+ title="Validate Folder Name")
+ ExtractConvertToEXR: ExtractConvertToEXRModel = Field(
+ default_factory=ExtractConvertToEXRModel,
+ title="Extract Convert To EXR")
+
+
+class LoadPluginsModel(BaseSettingsModel):
+ LoadImage: LoadImageModel = Field(
+ default_factory=LoadImageModel,
+ title="Load Image")
+ ImportImage: LoadImageModel = Field(
+ default_factory=LoadImageModel,
+ title="Import Image")
+
+
+DEFAULT_PUBLISH_SETTINGS = {
+ "CollectRenderInstances": {
+ "ignore_render_pass_transparency": False
+ },
+ "ExtractSequence": {
+ "review_bg": [255, 255, 255, 1.0]
+ },
+ "ValidateProjectSettings": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateMarks": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ValidateStartFrame": {
+ "enabled": False,
+ "optional": True,
+ "active": True
+ },
+ "ValidateAssetName": {
+ "enabled": True,
+ "optional": True,
+ "active": True
+ },
+ "ExtractConvertToEXR": {
+ "enabled": False,
+ "replace_pngs": True,
+ "exr_compression": "ZIP"
+ }
+}
diff --git a/server_addon/tvpaint/server/settings/workfile_builder.py b/server_addon/tvpaint/server/settings/workfile_builder.py
new file mode 100644
index 0000000000..e0aba5da7e
--- /dev/null
+++ b/server_addon/tvpaint/server/settings/workfile_builder.py
@@ -0,0 +1,30 @@
+from pydantic import Field
+
+from ayon_server.settings import (
+ BaseSettingsModel,
+ MultiplatformPathModel,
+ task_types_enum,
+)
+
+
+class CustomBuilderTemplate(BaseSettingsModel):
+ task_types: list[str] = Field(
+ default_factory=list,
+ title="Task types",
+ enum_resolver=task_types_enum
+ )
+ template_path: MultiplatformPathModel = Field(
+ default_factory=MultiplatformPathModel
+ )
+
+
+class WorkfileBuilderPlugin(BaseSettingsModel):
+ _title = "Workfile Builder"
+ create_first_version: bool = Field(
+ False,
+ title="Create first workfile"
+ )
+
+ custom_templates: list[CustomBuilderTemplate] = Field(
+ default_factory=CustomBuilderTemplate
+ )
diff --git a/server_addon/tvpaint/server/version.py b/server_addon/tvpaint/server/version.py
new file mode 100644
index 0000000000..3dc1f76bc6
--- /dev/null
+++ b/server_addon/tvpaint/server/version.py
@@ -0,0 +1 @@
+__version__ = "0.1.0"
diff --git a/server_addon/unreal/server/__init__.py b/server_addon/unreal/server/__init__.py
new file mode 100644
index 0000000000..a5f3e9597d
--- /dev/null
+++ b/server_addon/unreal/server/__init__.py
@@ -0,0 +1,19 @@
+from typing import Type
+
+from ayon_server.addons import BaseServerAddon
+
+from .version import __version__
+from .settings import UnrealSettings, DEFAULT_VALUES
+
+
+class UnrealAddon(BaseServerAddon):
+ name = "unreal"
+ title = "Unreal"
+ version = __version__
+ settings_model: Type[UnrealSettings] = UnrealSettings
+ frontend_scopes = {}
+ services = {}
+
+ async def get_default_settings(self):
+ settings_model_cls = self.get_settings_model()
+ return settings_model_cls(**DEFAULT_VALUES)
diff --git a/server_addon/unreal/server/imageio.py b/server_addon/unreal/server/imageio.py
new file mode 100644
index 0000000000..dde042ba47
--- /dev/null
+++ b/server_addon/unreal/server/imageio.py
@@ -0,0 +1,48 @@
+from pydantic import Field, validator
+from ayon_server.settings import BaseSettingsModel
+from ayon_server.settings.validators import ensure_unique_names
+
+
+class ImageIOConfigModel(BaseSettingsModel):
+ override_global_config: bool = Field(
+ False,
+ title="Override global OCIO config"
+ )
+ filepath: list[str] = Field(
+ default_factory=list,
+ title="Config path"
+ )
+
+
+class ImageIOFileRuleModel(BaseSettingsModel):
+ name: str = Field("", title="Rule name")
+ pattern: str = Field("", title="Regex pattern")
+ colorspace: str = Field("", title="Colorspace name")
+ ext: str = Field("", title="File extension")
+
+
+class ImageIOFileRulesModel(BaseSettingsModel):
+ activate_host_rules: bool = Field(False)
+ rules: list[ImageIOFileRuleModel] = Field(
+ default_factory=list,
+ title="Rules"
+ )
+
+ @validator("rules")
+ def validate_unique_outputs(cls, value):
+ ensure_unique_names(value)
+ return value
+
+
+class UnrealImageIOModel(BaseSettingsModel):
+ activate_host_color_management: bool = Field(
+ True, title="Enable Color Management"
+ )
+ ocio_config: ImageIOConfigModel = Field(
+ default_factory=ImageIOConfigModel,
+ title="OCIO config"
+ )
+ file_rules: ImageIOFileRulesModel = Field(
+ default_factory=ImageIOFileRulesModel,
+ title="File Rules"
+ )
diff --git a/server_addon/unreal/server/settings.py b/server_addon/unreal/server/settings.py
new file mode 100644
index 0000000000..479e041e25
--- /dev/null
+++ b/server_addon/unreal/server/settings.py
@@ -0,0 +1,64 @@
+from pydantic import Field
+from ayon_server.settings import BaseSettingsModel
+
+from .imageio import UnrealImageIOModel
+
+
+class ProjectSetup(BaseSettingsModel):
+ dev_mode: bool = Field(
+ False,
+ title="Dev mode"
+ )
+
+
+def _render_format_enum():
+ return [
+ {"value": "png", "label": "PNG"},
+ {"value": "exr", "label": "EXR"},
+ {"value": "jpg", "label": "JPG"},
+ {"value": "bmp", "label": "BMP"}
+ ]
+
+
+class UnrealSettings(BaseSettingsModel):
+ imageio: UnrealImageIOModel = Field(
+ default_factory=UnrealImageIOModel,
+ title="Color Management (ImageIO)"
+ )
+ level_sequences_for_layouts: bool = Field(
+ False,
+ title="Generate level sequences when loading layouts"
+ )
+ delete_unmatched_assets: bool = Field(
+ False,
+ title="Delete assets that are not matched"
+ )
+ render_config_path: str = Field(
+ "",
+ title="Render Config Path"
+ )
+ preroll_frames: int = Field(
+ 0,
+ title="Pre-roll frames"
+ )
+ render_format: str = Field(
+ "png",
+ title="Render format",
+ enum_resolver=_render_format_enum
+ )
+ project_setup: ProjectSetup = Field(
+ default_factory=ProjectSetup,
+ title="Project Setup",
+ )
+
+
+DEFAULT_VALUES = {
+ "level_sequences_for_layouts": False,
+ "delete_unmatched_assets": False,
+ "render_config_path": "",
+ "preroll_frames": 0,
+ "render_format": "png",
+ "project_setup": {
+ "dev_mode": False
+ }
+}
diff --git a/server_addon/unreal/server/version.py b/server_addon/unreal/server/version.py
new file mode 100644
index 0000000000..3dc1f76bc6
--- /dev/null
+++ b/server_addon/unreal/server/version.py
@@ -0,0 +1 @@
+__version__ = "0.1.0"
diff --git a/setup.py b/setup.py
index ab6e22bccc..4b6f286730 100644
--- a/setup.py
+++ b/setup.py
@@ -1,7 +1,6 @@
# -*- coding: utf-8 -*-
"""Setup info for building OpenPype 3.0."""
import os
-import sys
import re
import platform
import distutils.spawn
@@ -89,7 +88,6 @@ install_requires = [
"keyring",
"clique",
"jsonschema",
- "opentimelineio",
"pathlib2",
"pkg_resources",
"PIL",
@@ -158,11 +156,20 @@ bdist_mac_options = dict(
)
executables = [
- Executable("start.py", base=base,
- target_name="openpype_gui", icon=icon_path.as_posix()),
- Executable("start.py", base=None,
- target_name="openpype_console", icon=icon_path.as_posix())
+ Executable(
+ "start.py",
+ base=base,
+ target_name="openpype_gui",
+ icon=icon_path.as_posix()
+ ),
+ Executable(
+ "start.py",
+ base=None,
+ target_name="openpype_console",
+ icon=icon_path.as_posix()
+ ),
]
+
if IS_LINUX:
executables.append(
Executable(
diff --git a/start.py b/start.py
index 91e5c29a53..3b020c76c0 100644
--- a/start.py
+++ b/start.py
@@ -133,6 +133,10 @@ else:
vendor_python_path = os.path.join(OPENPYPE_ROOT, "vendor", "python")
sys.path.insert(0, vendor_python_path)
+# Add common package to sys path
+# - common contains common code for bootstraping and OpenPype processes
+sys.path.insert(0, os.path.join(OPENPYPE_ROOT, "common"))
+
import blessed # noqa: E402
import certifi # noqa: E402
@@ -140,8 +144,8 @@ import certifi # noqa: E402
if sys.__stdout__:
term = blessed.Terminal()
- def _print(message: str):
- if silent_mode:
+ def _print(message: str, force=False):
+ if silent_mode and not force:
return
if message.startswith("!!! "):
print(f'{term.orangered2("!!! ")}{message[4:]}')
@@ -197,6 +201,15 @@ if "--headless" in sys.argv:
elif os.getenv("OPENPYPE_HEADLESS_MODE") != "1":
os.environ.pop("OPENPYPE_HEADLESS_MODE", None)
+# Set builtin ocio root
+os.environ["BUILTIN_OCIO_ROOT"] = os.path.join(
+ OPENPYPE_ROOT,
+ "vendor",
+ "bin",
+ "ocioconfig",
+ "OpenColorIOConfigs"
+)
+
# Enabled logging debug mode when "--debug" is passed
if "--verbose" in sys.argv:
expected_values = (
@@ -255,6 +268,7 @@ from igniter import BootstrapRepos # noqa: E402
from igniter.tools import (
get_openpype_global_settings,
get_openpype_path_from_settings,
+ get_local_openpype_path_from_settings,
validate_mongo_connection,
OpenPypeVersionNotFound,
OpenPypeVersionIncompatible
@@ -348,8 +362,15 @@ def run_disk_mapping_commands(settings):
mappings = disk_mapping.get(low_platform) or []
for source, destination in mappings:
- destination = destination.rstrip('/')
- source = source.rstrip('/')
+ if low_platform == "windows":
+ destination = destination.replace("/", "\\").rstrip("\\")
+ source = source.replace("/", "\\").rstrip("\\")
+ # Add slash after ':' ('G:' -> 'G:\')
+ if destination.endswith(":"):
+ destination += "\\"
+ else:
+ destination = destination.rstrip("/")
+ source = source.rstrip("/")
if low_platform == "darwin":
scr = f'do shell script "ln -s {source} {destination}" with administrator privileges' # noqa
@@ -507,8 +528,8 @@ def _process_arguments() -> tuple:
not use_version_value
or not use_version_value.startswith("=")
):
- _print("!!! Please use option --use-version like:")
- _print(" --use-version=3.0.0")
+ _print("!!! Please use option --use-version like:", True)
+ _print(" --use-version=3.0.0", True)
sys.exit(1)
version_str = use_version_value[1:]
@@ -525,14 +546,14 @@ def _process_arguments() -> tuple:
break
if use_version is None:
- _print("!!! Requested version isn't in correct format.")
+ _print("!!! Requested version isn't in correct format.", True)
_print((" Use --list-versions to find out"
- " proper version string."))
+ " proper version string."), True)
sys.exit(1)
if arg == "--validate-version":
- _print("!!! Please use option --validate-version like:")
- _print(" --validate-version=3.0.0")
+ _print("!!! Please use option --validate-version like:", True)
+ _print(" --validate-version=3.0.0", True)
sys.exit(1)
if arg.startswith("--validate-version="):
@@ -543,9 +564,9 @@ def _process_arguments() -> tuple:
sys.argv.remove(arg)
commands.append("validate")
else:
- _print("!!! Requested version isn't in correct format.")
+ _print("!!! Requested version isn't in correct format.", True)
_print((" Use --list-versions to find out"
- " proper version string."))
+ " proper version string."), True)
sys.exit(1)
if "--list-versions" in sys.argv:
@@ -556,7 +577,7 @@ def _process_arguments() -> tuple:
# this is helper to run igniter before anything else
if "igniter" in sys.argv:
if os.getenv("OPENPYPE_HEADLESS_MODE") == "1":
- _print("!!! Cannot open Igniter dialog in headless mode.")
+ _print("!!! Cannot open Igniter dialog in headless mode.", True)
sys.exit(1)
return_code = igniter.open_dialog()
@@ -606,9 +627,9 @@ def _determine_mongodb() -> str:
if not openpype_mongo:
_print("*** No DB connection string specified.")
if os.getenv("OPENPYPE_HEADLESS_MODE") == "1":
- _print("!!! Cannot open Igniter dialog in headless mode.")
- _print(
- "!!! Please use `OPENPYPE_MONGO` to specify server address.")
+ _print("!!! Cannot open Igniter dialog in headless mode.", True)
+ _print(("!!! Please use `OPENPYPE_MONGO` to specify "
+ "server address."), True)
sys.exit(1)
_print("--- launching setup UI ...")
@@ -783,7 +804,7 @@ def _find_frozen_openpype(use_version: str = None,
try:
version_path = bootstrap.extract_openpype(openpype_version)
except OSError as e:
- _print("!!! failed: {}".format(str(e)))
+ _print("!!! failed: {}".format(str(e)), True)
sys.exit(1)
else:
# cleanup zip after extraction
@@ -899,7 +920,7 @@ def _boot_validate_versions(use_version, local_version):
v: OpenPypeVersion
found = [v for v in openpype_versions if str(v) == use_version]
if not found:
- _print(f"!!! Version [ {use_version} ] not found.")
+ _print(f"!!! Version [ {use_version} ] not found.", True)
list_versions(openpype_versions, local_version)
sys.exit(1)
@@ -908,7 +929,8 @@ def _boot_validate_versions(use_version, local_version):
use_version, openpype_versions
)
valid, message = bootstrap.validate_openpype_version(version_path)
- _print(f'{">>> " if valid else "!!! "}{message}')
+ _print(f'{">>> " if valid else "!!! "}{message}', not valid)
+ return valid
def _boot_print_versions(openpype_root):
@@ -935,7 +957,7 @@ def _boot_print_versions(openpype_root):
def _boot_handle_missing_version(local_version, message):
- _print(message)
+ _print(message, True)
if os.environ.get("OPENPYPE_HEADLESS_MODE") == "1":
openpype_versions = bootstrap.find_openpype(
include_zips=True)
@@ -983,7 +1005,7 @@ def boot():
openpype_mongo = _determine_mongodb()
except RuntimeError as e:
# without mongodb url we are done for.
- _print(f"!!! {e}")
+ _print(f"!!! {e}", True)
sys.exit(1)
os.environ["OPENPYPE_MONGO"] = openpype_mongo
@@ -1018,14 +1040,18 @@ def boot():
# find its versions there and bootstrap them.
openpype_path = get_openpype_path_from_settings(global_settings)
+ # Check if local versions should be installed in custom folder and not in
+ # user app data
+ data_dir = get_local_openpype_path_from_settings(global_settings)
+ bootstrap.set_data_dir(data_dir)
if getattr(sys, 'frozen', False):
local_version = bootstrap.get_version(Path(OPENPYPE_ROOT))
else:
local_version = OpenPypeVersion.get_installed_version_str()
if "validate" in commands:
- _boot_validate_versions(use_version, local_version)
- sys.exit(1)
+ valid = _boot_validate_versions(use_version, local_version)
+ sys.exit(0 if valid else 1)
if not openpype_path:
_print("*** Cannot get OpenPype path from database.")
@@ -1035,7 +1061,7 @@ def boot():
if "print_versions" in commands:
_boot_print_versions(OPENPYPE_ROOT)
- sys.exit(1)
+ sys.exit(0)
# ------------------------------------------------------------------------
# Find OpenPype versions
@@ -1052,13 +1078,13 @@ def boot():
except RuntimeError as e:
# no version to run
- _print(f"!!! {e}")
+ _print(f"!!! {e}", True)
sys.exit(1)
# validate version
- _print(f">>> Validating version [ {str(version_path)} ]")
+ _print(f">>> Validating version in frozen [ {str(version_path)} ]")
result = bootstrap.validate_openpype_version(version_path)
if not result[0]:
- _print(f"!!! Invalid version: {result[1]}")
+ _print(f"!!! Invalid version: {result[1]}", True)
sys.exit(1)
_print("--- version is valid")
else:
@@ -1126,7 +1152,7 @@ def boot():
cli.main(obj={}, prog_name="openpype")
except Exception: # noqa
exc_info = sys.exc_info()
- _print("!!! OpenPype crashed:")
+ _print("!!! OpenPype crashed:", True)
traceback.print_exception(*exc_info)
sys.exit(1)
diff --git a/tests/integration/hosts/nuke/test_deadline_publish_in_nuke_prerender.py b/tests/integration/hosts/nuke/test_deadline_publish_in_nuke_prerender.py
new file mode 100644
index 0000000000..57e2f78973
--- /dev/null
+++ b/tests/integration/hosts/nuke/test_deadline_publish_in_nuke_prerender.py
@@ -0,0 +1,106 @@
+import logging
+
+from tests.lib.assert_classes import DBAssert
+from tests.integration.hosts.nuke.lib import NukeDeadlinePublishTestClass
+
+log = logging.getLogger("test_publish_in_nuke")
+
+
+class TestDeadlinePublishInNukePrerender(NukeDeadlinePublishTestClass):
+ """Basic test case for publishing in Nuke and Deadline for prerender
+
+ It is different from `test_deadline_publish_in_nuke` as that one is for
+ `render` family >> this test expects different subset names.
+
+ Uses generic TestCase to prepare fixtures for test data, testing DBs,
+ env vars.
+
+ !!!
+ It expects path in WriteNode starting with 'c:/projects', it replaces
+ it with correct value in temp folder.
+ Access file path by selecting WriteNode group, CTRL+Enter, update file
+ input
+ !!!
+
+ Opens Nuke, run publish on prepared workile.
+
+ Then checks content of DB (if subset, version, representations were
+ created.
+ Checks tmp folder if all expected files were published.
+
+ How to run:
+ (in cmd with activated {OPENPYPE_ROOT}/.venv)
+ {OPENPYPE_ROOT}/.venv/Scripts/python.exe {OPENPYPE_ROOT}/start.py
+ runtests ../tests/integration/hosts/nuke # noqa: E501
+
+ To check log/errors from launched app's publish process keep PERSIST
+ to True and check `test_openpype.logs` collection.
+ """
+ TEST_FILES = [
+ ("1aQaKo3cF-fvbTfvODIRFMxgherjbJ4Ql",
+ "test_nuke_deadline_publish_in_nuke_prerender.zip", "")
+ ]
+
+ APP_GROUP = "nuke"
+
+ TIMEOUT = 180 # publish timeout
+
+ # could be overwritten by command line arguments
+ # keep empty to locate latest installed variant or explicit
+ APP_VARIANT = ""
+ PERSIST = False # True - keep test_db, test_openpype, outputted test files
+ TEST_DATA_FOLDER = None
+
+ def test_db_asserts(self, dbcon, publish_finished):
+ """Host and input data dependent expected results in DB."""
+ print("test_db_asserts")
+ failures = []
+
+ failures.append(DBAssert.count_of_types(dbcon, "version", 2))
+
+ failures.append(
+ DBAssert.count_of_types(dbcon, "version", 0, name={"$ne": 1}))
+
+ # prerender has only default subset format `{family}{variant}`,
+ # Key01 is used variant
+ failures.append(
+ DBAssert.count_of_types(dbcon, "subset", 1,
+ name="prerenderKey01"))
+
+ failures.append(
+ DBAssert.count_of_types(dbcon, "subset", 1,
+ name="workfileTest_task"))
+
+ failures.append(
+ DBAssert.count_of_types(dbcon, "representation", 2))
+
+ additional_args = {"context.subset": "workfileTest_task",
+ "context.ext": "nk"}
+ failures.append(
+ DBAssert.count_of_types(dbcon, "representation", 1,
+ additional_args=additional_args))
+
+ additional_args = {"context.subset": "prerenderKey01",
+ "context.ext": "exr"}
+ failures.append(
+ DBAssert.count_of_types(dbcon, "representation", 1,
+ additional_args=additional_args))
+
+ # prerender doesn't have set creation of review by default
+ additional_args = {"context.subset": "prerenderKey01",
+ "name": "thumbnail"}
+ failures.append(
+ DBAssert.count_of_types(dbcon, "representation", 0,
+ additional_args=additional_args))
+
+ additional_args = {"context.subset": "prerenderKey01",
+ "name": "h264_mov"}
+ failures.append(
+ DBAssert.count_of_types(dbcon, "representation", 0,
+ additional_args=additional_args))
+
+ assert not any(failures)
+
+
+if __name__ == "__main__":
+ test_case = TestDeadlinePublishInNukePrerender()
diff --git a/common/openpype_common/distribution/file_handler.py b/tests/lib/file_handler.py
similarity index 66%
rename from common/openpype_common/distribution/file_handler.py
rename to tests/lib/file_handler.py
index e649f143e9..07f6962c98 100644
--- a/common/openpype_common/distribution/file_handler.py
+++ b/tests/lib/file_handler.py
@@ -9,21 +9,23 @@ import hashlib
import tarfile
import zipfile
+import requests
-USER_AGENT = "openpype"
+USER_AGENT = "AYON-launcher"
class RemoteFileHandler:
"""Download file from url, might be GDrive shareable link"""
- IMPLEMENTED_ZIP_FORMATS = ['zip', 'tar', 'tgz',
- 'tar.gz', 'tar.xz', 'tar.bz2']
+ IMPLEMENTED_ZIP_FORMATS = {
+ "zip", "tar", "tgz", "tar.gz", "tar.xz", "tar.bz2"
+ }
@staticmethod
def calculate_md5(fpath, chunk_size=10000):
md5 = hashlib.md5()
- with open(fpath, 'rb') as f:
- for chunk in iter(lambda: f.read(chunk_size), b''):
+ with open(fpath, "rb") as f:
+ for chunk in iter(lambda: f.read(chunk_size), b""):
md5.update(chunk)
return md5.hexdigest()
@@ -45,7 +47,7 @@ class RemoteFileHandler:
h = hashlib.sha256()
b = bytearray(128 * 1024)
mv = memoryview(b)
- with open(fpath, 'rb', buffering=0) as f:
+ with open(fpath, "rb", buffering=0) as f:
for n in iter(lambda: f.readinto(mv), 0):
h.update(mv[:n])
return h.hexdigest()
@@ -62,27 +64,32 @@ class RemoteFileHandler:
return True
if not hash_type:
raise ValueError("Provide hash type, md5 or sha256")
- if hash_type == 'md5':
+ if hash_type == "md5":
return RemoteFileHandler.check_md5(fpath, hash_value)
if hash_type == "sha256":
return RemoteFileHandler.check_sha256(fpath, hash_value)
@staticmethod
def download_url(
- url, root, filename=None,
- sha256=None, max_redirect_hops=3
+ url,
+ root,
+ filename=None,
+ max_redirect_hops=3,
+ headers=None
):
- """Download a file from a url and place it in root.
+ """Download a file from url and place it in root.
+
Args:
url (str): URL to download file from
root (str): Directory to place downloaded file in
filename (str, optional): Name to save the file under.
If None, use the basename of the URL
- sha256 (str, optional): sha256 checksum of the download.
- If None, do not check
- max_redirect_hops (int, optional): Maximum number of redirect
+ max_redirect_hops (Optional[int]): Maximum number of redirect
hops allowed
+ headers (Optional[dict[str, str]]): Additional required headers
+ - Authentication etc..
"""
+
root = os.path.expanduser(root)
if not filename:
filename = os.path.basename(url)
@@ -90,55 +97,44 @@ class RemoteFileHandler:
os.makedirs(root, exist_ok=True)
- # check if file is already present locally
- if RemoteFileHandler.check_integrity(fpath,
- sha256, hash_type="sha256"):
- print('Using downloaded and verified file: ' + fpath)
- return
-
# expand redirect chain if needed
- url = RemoteFileHandler._get_redirect_url(url,
- max_hops=max_redirect_hops)
+ url = RemoteFileHandler._get_redirect_url(
+ url, max_hops=max_redirect_hops, headers=headers)
# check if file is located on Google Drive
file_id = RemoteFileHandler._get_google_drive_file_id(url)
if file_id is not None:
return RemoteFileHandler.download_file_from_google_drive(
- file_id, root, filename, sha256)
+ file_id, root, filename)
# download the file
try:
- print('Downloading ' + url + ' to ' + fpath)
- RemoteFileHandler._urlretrieve(url, fpath)
- except (urllib.error.URLError, IOError) as e:
- if url[:5] == 'https':
- url = url.replace('https:', 'http:')
- print('Failed download. Trying https -> http instead.'
- ' Downloading ' + url + ' to ' + fpath)
- RemoteFileHandler._urlretrieve(url, fpath)
- else:
- raise e
+ print(f"Downloading {url} to {fpath}")
+ RemoteFileHandler._urlretrieve(url, fpath, headers=headers)
+ except (urllib.error.URLError, IOError) as exc:
+ if url[:5] != "https":
+ raise exc
- # check integrity of downloaded file
- if not RemoteFileHandler.check_integrity(fpath,
- sha256, hash_type="sha256"):
- raise RuntimeError("File not found or corrupted.")
+ url = url.replace("https:", "http:")
+ print((
+ "Failed download. Trying https -> http instead."
+ f" Downloading {url} to {fpath}"
+ ))
+ RemoteFileHandler._urlretrieve(url, fpath, headers=headers)
@staticmethod
- def download_file_from_google_drive(file_id, root,
- filename=None,
- sha256=None):
+ def download_file_from_google_drive(
+ file_id, root, filename=None
+ ):
"""Download a Google Drive file from and place it in root.
Args:
file_id (str): id of file to be downloaded
root (str): Directory to place downloaded file in
filename (str, optional): Name to save the file under.
If None, use the id of the file.
- sha256 (str, optional): sha256 checksum of the download.
- If None, do not check
"""
# Based on https://stackoverflow.com/questions/38511444/python-download-files-from-google-drive-using-url # noqa
- import requests
+
url = "https://docs.google.com/uc?export=download"
root = os.path.expanduser(root)
@@ -148,17 +144,16 @@ class RemoteFileHandler:
os.makedirs(root, exist_ok=True)
- if os.path.isfile(fpath) and RemoteFileHandler.check_integrity(
- fpath, sha256, hash_type="sha256"):
- print('Using downloaded and verified file: ' + fpath)
+ if os.path.isfile(fpath) and RemoteFileHandler.check_integrity(fpath):
+ print(f"Using downloaded and verified file: {fpath}")
else:
session = requests.Session()
- response = session.get(url, params={'id': file_id}, stream=True)
+ response = session.get(url, params={"id": file_id}, stream=True)
token = RemoteFileHandler._get_confirm_token(response)
if token:
- params = {'id': file_id, 'confirm': token}
+ params = {"id": file_id, "confirm": token}
response = session.get(url, params=params, stream=True)
response_content_generator = response.iter_content(32768)
@@ -186,28 +181,28 @@ class RemoteFileHandler:
destination_path = os.path.dirname(path)
_, archive_type = os.path.splitext(path)
- archive_type = archive_type.lstrip('.')
+ archive_type = archive_type.lstrip(".")
- if archive_type in ['zip']:
- print("Unzipping {}->{}".format(path, destination_path))
+ if archive_type in ["zip"]:
+ print(f"Unzipping {path}->{destination_path}")
zip_file = zipfile.ZipFile(path)
zip_file.extractall(destination_path)
zip_file.close()
elif archive_type in [
- 'tar', 'tgz', 'tar.gz', 'tar.xz', 'tar.bz2'
+ "tar", "tgz", "tar.gz", "tar.xz", "tar.bz2"
]:
- print("Unzipping {}->{}".format(path, destination_path))
- if archive_type == 'tar':
- tar_type = 'r:'
- elif archive_type.endswith('xz'):
- tar_type = 'r:xz'
- elif archive_type.endswith('gz'):
- tar_type = 'r:gz'
- elif archive_type.endswith('bz2'):
- tar_type = 'r:bz2'
+ print(f"Unzipping {path}->{destination_path}")
+ if archive_type == "tar":
+ tar_type = "r:"
+ elif archive_type.endswith("xz"):
+ tar_type = "r:xz"
+ elif archive_type.endswith("gz"):
+ tar_type = "r:gz"
+ elif archive_type.endswith("bz2"):
+ tar_type = "r:bz2"
else:
- tar_type = 'r:*'
+ tar_type = "r:*"
try:
tar_file = tarfile.open(path, tar_type)
except tarfile.ReadError:
@@ -216,29 +211,35 @@ class RemoteFileHandler:
tar_file.close()
@staticmethod
- def _urlretrieve(url, filename, chunk_size):
+ def _urlretrieve(url, filename, chunk_size=None, headers=None):
+ final_headers = {"User-Agent": USER_AGENT}
+ if headers:
+ final_headers.update(headers)
+
+ chunk_size = chunk_size or 8192
with open(filename, "wb") as fh:
with urllib.request.urlopen(
- urllib.request.Request(url,
- headers={"User-Agent": USER_AGENT})) \
- as response:
+ urllib.request.Request(url, headers=final_headers)
+ ) as response:
for chunk in iter(lambda: response.read(chunk_size), ""):
if not chunk:
break
fh.write(chunk)
@staticmethod
- def _get_redirect_url(url, max_hops):
+ def _get_redirect_url(url, max_hops, headers=None):
initial_url = url
- headers = {"Method": "HEAD", "User-Agent": USER_AGENT}
-
+ final_headers = {"Method": "HEAD", "User-Agent": USER_AGENT}
+ if headers:
+ final_headers.update(headers)
for _ in range(max_hops + 1):
with urllib.request.urlopen(
- urllib.request.Request(url, headers=headers)) as response:
+ urllib.request.Request(url, headers=final_headers)
+ ) as response:
if response.url == url or response.url is None:
return url
- url = response.url
+ return response.url
else:
raise RecursionError(
f"Request to {initial_url} exceeded {max_hops} redirects. "
@@ -248,7 +249,7 @@ class RemoteFileHandler:
@staticmethod
def _get_confirm_token(response):
for key, value in response.cookies.items():
- if key.startswith('download_warning'):
+ if key.startswith("download_warning"):
return value
# handle antivirus warning for big zips
diff --git a/tests/lib/testing_classes.py b/tests/lib/testing_classes.py
index 300024dc98..2af4af02de 100644
--- a/tests/lib/testing_classes.py
+++ b/tests/lib/testing_classes.py
@@ -12,7 +12,7 @@ import requests
import re
from tests.lib.db_handler import DBHandler
-from common.openpype_common.distribution.file_handler import RemoteFileHandler
+from tests.lib.file_handler import RemoteFileHandler
from openpype.modules import ModulesManager
from openpype.settings import get_project_settings
diff --git a/tests/unit/openpype/default_modules/royal_render/test_rr_job.py b/tests/unit/openpype/default_modules/royal_render/test_rr_job.py
deleted file mode 100644
index ab8b1bfd50..0000000000
--- a/tests/unit/openpype/default_modules/royal_render/test_rr_job.py
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- coding: utf-8 -*-
-"""Test suite for User Settings."""
-# import pytest
-# from openpype.modules import ModulesManager
-
-
-def test_rr_job():
- # manager = ModulesManager()
- # rr_module = manager.modules_by_name["royalrender"]
- ...
diff --git a/tools/fetch_thirdparty_libs.py b/tools/fetch_thirdparty_libs.py
index 70257caa46..c2dc4636d0 100644
--- a/tools/fetch_thirdparty_libs.py
+++ b/tools/fetch_thirdparty_libs.py
@@ -67,40 +67,45 @@ def _print(msg: str, message_type: int = 0) -> None:
print(f"{header}{msg}")
-def install_qtbinding(pyproject, openpype_root, platform_name):
- _print("Handling Qt binding framework ...")
- qtbinding_def = pyproject["openpype"]["qtbinding"][platform_name]
- package = qtbinding_def["package"]
- version = qtbinding_def.get("version")
-
- qtbinding_arg = None
+def _pip_install(openpype_root, package, version=None):
+ arg = None
if package and version:
- qtbinding_arg = f"{package}=={version}"
+ arg = f"{package}=={version}"
elif package:
- qtbinding_arg = package
+ arg = package
- if not qtbinding_arg:
- _print("Didn't find Qt binding to install")
+ if not arg:
+ _print("Couldn't find package to install")
sys.exit(1)
- _print(f"We'll install {qtbinding_arg}")
+ _print(f"We'll install {arg}")
python_vendor_dir = openpype_root / "vendor" / "python"
try:
subprocess.run(
[
sys.executable,
- "-m", "pip", "install", "--upgrade", qtbinding_arg,
+ "-m", "pip", "install", "--upgrade", arg,
"-t", str(python_vendor_dir)
],
check=True,
stdout=subprocess.DEVNULL
)
except subprocess.CalledProcessError as e:
- _print("Error during PySide2 installation.", 1)
+ _print(f"Error during {package} installation.", 1)
_print(str(e), 1)
sys.exit(1)
+
+def install_qtbinding(pyproject, openpype_root, platform_name):
+ _print("Handling Qt binding framework ...")
+ qtbinding_def = pyproject["openpype"]["qtbinding"][platform_name]
+ package = qtbinding_def["package"]
+ version = qtbinding_def.get("version")
+ _pip_install(openpype_root, package, version)
+
+ python_vendor_dir = openpype_root / "vendor" / "python"
+
# Remove libraries for QtSql which don't have available libraries
# by default and Postgre library would require to modify rpath of
# dependency
@@ -112,6 +117,13 @@ def install_qtbinding(pyproject, openpype_root, platform_name):
os.remove(str(filepath))
+def install_runtime_dependencies(pyproject, openpype_root):
+ _print("Installing Runtime Dependencies ...")
+ runtime_deps = pyproject["openpype"]["runtime-deps"]
+ for package, version in runtime_deps.items():
+ _pip_install(openpype_root, package, version)
+
+
def install_thirdparty(pyproject, openpype_root, platform_name):
_print("Processing third-party dependencies ...")
try:
@@ -221,6 +233,7 @@ def main():
pyproject = toml.load(openpype_root / "pyproject.toml")
platform_name = platform.system().lower()
install_qtbinding(pyproject, openpype_root, platform_name)
+ install_runtime_dependencies(pyproject, openpype_root)
install_thirdparty(pyproject, openpype_root, platform_name)
end_time = time.time_ns()
total_time = (end_time - start_time) / 1000000000
diff --git a/website/docs/admin_hosts_maya.md b/website/docs/admin_hosts_maya.md
index 700822843f..93acf316c2 100644
--- a/website/docs/admin_hosts_maya.md
+++ b/website/docs/admin_hosts_maya.md
@@ -113,7 +113,8 @@ This is useful to fix some specific renderer glitches and advanced hacking of Ma
#### Namespace and Group Name
Here you can create your own custom naming for the reference loader.
-The custom naming is split into two parts: namespace and group name. If you don't set the namespace or the group name, an error will occur.
+The custom naming is split into two parts: namespace and group name. If you don't set the namespace, an error will occur.
+Group name could be set empty, that way no wrapping group will be created for loaded item.
Here's the different variables you can use:
diff --git a/website/docs/admin_hosts_resolve.md b/website/docs/admin_hosts_resolve.md
index 09e7df1d9f..8bb8440f78 100644
--- a/website/docs/admin_hosts_resolve.md
+++ b/website/docs/admin_hosts_resolve.md
@@ -4,100 +4,38 @@ title: DaVinci Resolve Setup
sidebar_label: DaVinci Resolve
---
-import Tabs from '@theme/Tabs';
-import TabItem from '@theme/TabItem';
+:::warning
+Only Resolve Studio is supported due to Python API limitation in Resolve (free).
+:::
## Resolve requirements
Due to the way resolve handles python and python scripts there are a few steps required steps needed to be done on any machine that will be using OpenPype with resolve.
-### Installing Resolve's own python 3.6 interpreter.
-Resolve uses a hardcoded method to look for the python executable path. All of tho following paths are defined automatically by Python msi installer. We are using Python 3.6.2.
+## Basic setup
-
+- Supported version is up to v18
+- Install Python 3.6.2 (latest tested v17) or up to 3.9.13 (latest tested on v18)
+- pip install PySide2:
+ - Python 3.9.*: open terminal and go to python.exe directory, then `python -m pip install PySide2`
+- pip install OpenTimelineIO:
+ - Python 3.9.*: open terminal and go to python.exe directory, then `python -m pip install OpenTimelineIO`
+ - Python 3.6: open terminal and go to python.exe directory, then `python -m pip install git+https://github.com/PixarAnimationStudios/OpenTimelineIO.git@5aa24fbe89d615448876948fe4b4900455c9a3e8` and move built files from `./Lib/site-packages/opentimelineio/cxx-libs/bin and lib` to `./Lib/site-packages/opentimelineio/`. I was building it on Win10 machine with Visual Studio Community 2019 and
+  with installed CMake in PATH.
+- make sure Resolve Fusion (Fusion Tab/menu/Fusion/Fusion Settings) is set to Python 3.6
+ 
+- Open OpenPype **Tray/Admin/Studio settings** > `applications/resolve/environment` and add Python3 path to `RESOLVE_PYTHON3_HOME` platform related.
-
+## Editorial setup
-`%LOCALAPPDATA%\Programs\Python\Python36`
+This is how it looks on my testing project timeline
+
+Notice I had renamed tracks to `main` (holding metadata markers) and `review` used for generating review data with ffmpeg confersion to jpg sequence.
-
-
-
-`/opt/Python/3.6/bin`
-
-
-
-
-`~/Library/Python/3.6/bin`
-
-
-
-
-
-### Installing PySide2 into python 3.6 for correct gui work
-
-OpenPype is using its own window widget inside Resolve, for that reason PySide2 has to be installed into the python 3.6 (as explained above).
-
-
-
-
-
-paste to any terminal of your choice
-
-```bash
-%LOCALAPPDATA%\Programs\Python\Python36\python.exe -m pip install PySide2
-```
-
-
-
-
-paste to any terminal of your choice
-
-```bash
-/opt/Python/3.6/bin/python -m pip install PySide2
-```
-
-
-
-
-paste to any terminal of your choice
-
-```bash
-~/Library/Python/3.6/bin/python -m pip install PySide2
-```
-
-
-
-
-
-
-### Set Resolve's Fusion settings for Python 3.6 interpereter
-
-
-
-
-As it is shown in below picture you have to go to Fusion Tab and then in Fusion menu find Fusion Settings. Go to Fusion/Script and find Default Python Version and switch to Python 3.6
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+1. you need to start OpenPype menu from Resolve/EditTab/Menu/Workspace/Scripts/Comp/**__OpenPype_Menu__**
+2. then select any clips in `main` track and change their color to `Chocolate`
+3. in OpenPype Menu select `Create`
+4. in Creator select `Create Publishable Clip [New]` (temporary name)
+5. set `Rename clips` to True, Master Track to `main` and Use review track to `review` as in picture
+ 
+6. after you hit `ok` all clips are colored to `ping` and marked with openpype metadata tag
+7. git `Publish` on openpype menu and see that all had been collected correctly. That is the last step for now as rest is Work in progress. Next steps will follow.
diff --git a/website/docs/admin_openpype_commands.md b/website/docs/admin_openpype_commands.md
index 131b6c0a51..a149d78aa2 100644
--- a/website/docs/admin_openpype_commands.md
+++ b/website/docs/admin_openpype_commands.md
@@ -40,7 +40,6 @@ For more information [see here](admin_use.md#run-openpype).
| module | Run command line arguments for modules. | |
| repack-version | Tool to re-create version zip. | [📑](#repack-version-arguments) |
| tray | Launch OpenPype Tray. | [📑](#tray-arguments)
-| launch | Launch application in Pype environment. | [📑](#launch-arguments) |
| 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) |
@@ -54,26 +53,6 @@ For more information [see here](admin_use.md#run-openpype).
```shell
openpype_console tray
```
----
-
-### `launch` arguments {#launch-arguments}
-
-| Argument | Description |
-| --- | --- |
-| `--app` | Application name - this should be the key for application from Settings. |
-| `--project` | Project name (default taken from `AVALON_PROJECT` if set) |
-| `--asset` | Asset name (default taken from `AVALON_ASSET` if set) |
-| `--task` | Task name (default taken from `AVALON_TASK` is set) |
-| `--tools` | *Optional: Additional tools to add* |
-| `--user` | *Optional: User on behalf to run* |
-| `--ftrack-server` / `-fs` | *Optional: Ftrack server URL* |
-| `--ftrack-user` / `-fu` | *Optional: Ftrack user* |
-| `--ftrack-key` / `-fk` | *Optional: Ftrack API key* |
-
-For example to run Python interactive console in Pype context:
-```shell
-pype launch --app python --project my_project --asset my_asset --task my_task
-```
---
### `publish` arguments {#publish-arguments}
diff --git a/website/docs/admin_settings_system.md b/website/docs/admin_settings_system.md
index d61713ccd5..8abcefd24d 100644
--- a/website/docs/admin_settings_system.md
+++ b/website/docs/admin_settings_system.md
@@ -102,6 +102,10 @@ workstation that should be submitting render jobs to muster via OpenPype.
**`templates mapping`** - you can customize Muster templates to match your existing setup here.
+### Royal Render
+
+**`Royal Render Root Paths`** - multi platform paths to Royal Render installation.
+
### Clockify
**`Workspace Name`** - name of the clockify workspace where you would like to be sending all the timelogs.
diff --git a/website/docs/artist_hosts_houdini.md b/website/docs/artist_hosts_houdini.md
index 0471765365..940d5ac351 100644
--- a/website/docs/artist_hosts_houdini.md
+++ b/website/docs/artist_hosts_houdini.md
@@ -132,3 +132,25 @@ switch versions between different hda types.
When you load hda, it will install its type in your hip file and add published version as its definition file. When
you switch version via Scene Manager, it will add its definition and set it as preferred.
+
+## Publishing and loading BGEO caches
+
+There is a simple support for publishing and loading **BGEO** files in all supported compression variants.
+
+### Creating BGEO instances
+
+Select your SOP node to be exported as BGEO. If your selection is in the object level, OpenPype will try to find if there is an `output` node inside, the one with the lowest index will be used:
+
+
+
+Then you can open Publisher, in Create you select **BGEO PointCache**:
+
+
+
+You can select compression type and if the current selection should be connected to ROPs SOP path parameter. Publishing will produce sequence of files based on your timeline settings.
+
+### Loading BGEO
+
+Select your published BGEO subsets in Loader, right click and load them in:
+
+
diff --git a/website/docs/assets/houdini_bgeo-loading.png b/website/docs/assets/houdini_bgeo-loading.png
new file mode 100644
index 0000000000..e8aad66f43
Binary files /dev/null and b/website/docs/assets/houdini_bgeo-loading.png differ
diff --git a/website/docs/assets/houdini_bgeo-publisher.png b/website/docs/assets/houdini_bgeo-publisher.png
new file mode 100644
index 0000000000..5c3534077f
Binary files /dev/null and b/website/docs/assets/houdini_bgeo-publisher.png differ
diff --git a/website/docs/assets/houdini_bgeo_output_node.png b/website/docs/assets/houdini_bgeo_output_node.png
new file mode 100644
index 0000000000..160f0a259b
Binary files /dev/null and b/website/docs/assets/houdini_bgeo_output_node.png differ
diff --git a/website/docs/dev_colorspace.md b/website/docs/dev_colorspace.md
index c4b8e74d73..cb07cb18a0 100644
--- a/website/docs/dev_colorspace.md
+++ b/website/docs/dev_colorspace.md
@@ -80,7 +80,7 @@ from openpype.pipeline.colorspace import (
class YourLoader(api.Loader):
def load(self, context, name=None, namespace=None, options=None):
- path = self.fname
+ path = self.filepath_from_context(context)
colorspace_data = context["representation"]["data"].get("colorspaceData", {})
colorspace = (
colorspace_data.get("colorspace")
diff --git a/website/docs/module_royalrender.md b/website/docs/module_royalrender.md
new file mode 100644
index 0000000000..2b75fbefef
--- /dev/null
+++ b/website/docs/module_royalrender.md
@@ -0,0 +1,37 @@
+---
+id: module_royalrender
+title: Royal Render Administration
+sidebar_label: Royal Render
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+
+
+## Preparation
+
+For [Royal Render](hhttps://www.royalrender.de/) support you need to set a few things up in both OpenPype and Royal Render itself
+
+1. Deploy OpenPype executable to all nodes of Royal Render farm. See [Install & Run](admin_use.md)
+
+2. Enable Royal Render Module in the [OpenPype Admin Settings](admin_settings_system.md#royal-render).
+
+3. Point OpenPype to your Royal Render installation in the [OpenPype Admin Settings](admin_settings_system.md#royal-render).
+
+4. Install our custom plugin and scripts to your RR repository. It should be as simple as copying content of `openpype/modules/royalrender/rr_root` to `path/to/your/royalrender/repository`.
+
+
+## Configuration
+
+OpenPype integration for Royal Render consists of pointing RR to location of Openpype executable. That is being done by copying `_install_paths/OpenPype.cfg` to
+RR root folder. This file contains reasonable defaults. They could be changed in this file or modified Render apps in `rrControl`.
+
+
+## Debugging
+
+Current implementation uses dynamically build '.xml' file which is stored in temporary folder accessible by RR. It might make sense to
+use this Openpype built file and try to run it via `*__rrServerConsole` executable from command line in case of unforeseeable issues.
+
+## Known issues
+
+Currently environment values set in Openpype are not propagated into render jobs on RR. It is studio responsibility to synchronize environment variables from Openpype with all render nodes for now.
diff --git a/website/sidebars.js b/website/sidebars.js
index 267cc7f6d7..b885181fb6 100644
--- a/website/sidebars.js
+++ b/website/sidebars.js
@@ -111,6 +111,7 @@ module.exports = {
"module_site_sync",
"module_deadline",
"module_muster",
+ "module_royalrender",
"module_clockify",
"module_slack"
],