Merge branch 'develop' into backup/app_group_in_name

# Conflicts:
#	openpype/lib/applications.py
#	pype/hosts/premiere/extensions/com.pype/jsx/pype.jsx
This commit is contained in:
iLLiCiTiT 2021-04-06 11:16:56 +02:00
commit 9a6792ffab
1717 changed files with 4525 additions and 57466 deletions

View file

@ -0,0 +1,23 @@
from .lib import (
get_system_settings,
get_project_settings,
get_current_project_settings,
get_anatomy_settings,
get_environments
)
from .entities import (
SystemSettings,
ProjectSettings
)
__all__ = (
"get_system_settings",
"get_project_settings",
"get_current_project_settings",
"get_anatomy_settings",
"get_environments",
"SystemSettings",
"ProjectSettings"
)

View file

@ -0,0 +1,46 @@
import re
# Metadata keys for work with studio and project overrides
M_OVERRIDEN_KEY = "__overriden_keys__"
# Metadata key for storing information about environments
M_ENVIRONMENT_KEY = "__environment_keys__"
# Metadata key for storing dynamic created labels
M_DYNAMIC_KEY_LABEL = "__dynamic_keys_labels__"
METADATA_KEYS = (
M_OVERRIDEN_KEY,
M_ENVIRONMENT_KEY,
M_DYNAMIC_KEY_LABEL
)
# File where studio's system overrides are stored
GLOBAL_SETTINGS_KEY = "global_settings"
SYSTEM_SETTINGS_KEY = "system_settings"
PROJECT_SETTINGS_KEY = "project_settings"
PROJECT_ANATOMY_KEY = "project_anatomy"
LOCAL_SETTING_KEY = "local_settings"
DEFAULT_PROJECT_KEY = "__default_project__"
KEY_ALLOWED_SYMBOLS = "a-zA-Z0-9-_ "
KEY_REGEX = re.compile(r"^[{}]+$".format(KEY_ALLOWED_SYMBOLS))
__all__ = (
"M_OVERRIDEN_KEY",
"M_ENVIRONMENT_KEY",
"M_DYNAMIC_KEY_LABEL",
"METADATA_KEYS",
"SYSTEM_SETTINGS_KEY",
"PROJECT_SETTINGS_KEY",
"PROJECT_ANATOMY_KEY",
"LOCAL_SETTING_KEY",
"DEFAULT_PROJECT_KEY",
"KEY_ALLOWED_SYMBOLS",
"KEY_REGEX"
)

View file

@ -0,0 +1,26 @@
{
"fps": 25.0,
"frameStart": 1001,
"frameEnd": 1001,
"clipIn": 1,
"clipOut": 1,
"handleStart": 0,
"handleEnd": 0,
"resolutionWidth": 1920,
"resolutionHeight": 1080,
"pixelAspect": 1.0,
"applications": [
"maya_2020",
"nuke_12-2",
"nukex_12-2",
"hiero_12-2",
"resolve_16",
"houdini_18-5",
"blender_2-90",
"harmony_20",
"photoshop_2021",
"aftereffects_2021",
"unreal_4-24"
],
"tools_env": []
}

View file

@ -0,0 +1,129 @@
{
"hiero": {
"workfile": {
"ocioConfigName": "nuke-default",
"ocioconfigpath": {
"windows": [],
"darwin": [],
"linux": []
},
"workingSpace": "linear",
"sixteenBitLut": "sRGB",
"eightBitLut": "sRGB",
"floatLut": "linear",
"logLut": "Cineon",
"viewerLut": "sRGB",
"thumbnailLut": "sRGB"
},
"regexInputs": {
"inputs": [
{
"regex": "[^-a-zA-Z0-9](plateRef).*(?=mp4)",
"colorspace": "sRGB"
}
]
}
},
"nuke": {
"workfile": {
"colorManagement": "Nuke",
"OCIO_config": "nuke-default",
"customOCIOConfigPath": {
"windows": [],
"darwin": [],
"linux": []
},
"workingSpaceLUT": "linear",
"monitorLut": "sRGB",
"int8Lut": "sRGB",
"int16Lut": "sRGB",
"logLut": "Cineon",
"floatLut": "linear"
},
"nodes": {
"requiredNodes": [
{
"plugins": [
"CreateWriteRender"
],
"nukeNodeClass": "Write",
"knobs": [
{
"name": "file_type",
"value": "exr"
},
{
"name": "datatype",
"value": "16 bit half"
},
{
"name": "compression",
"value": "Zip (1 scanline)"
},
{
"name": "autocrop",
"value": "True"
},
{
"name": "tile_color",
"value": "0xff0000ff"
},
{
"name": "channels",
"value": "rgb"
},
{
"name": "colorspace",
"value": "linear"
}
]
},
{
"plugins": [
"CreateWritePrerender"
],
"nukeNodeClass": "Write",
"knobs": [
{
"name": "file_type",
"value": "exr"
},
{
"name": "datatype",
"value": "16 bit half"
},
{
"name": "compression",
"value": "Zip (1 scanline)"
},
{
"name": "autocrop",
"value": "False"
},
{
"name": "tile_color",
"value": "0xff0000ff"
},
{
"name": "channels",
"value": "rgb"
},
{
"name": "colorspace",
"value": "linear"
}
]
}
],
"customNodes": []
},
"regexInputs": {
"inputs": [
{
"regex": "[^-a-zA-Z0-9]beauty[^-a-zA-Z0-9]",
"colorspace": "linear"
}
]
}
}
}

View file

@ -0,0 +1,7 @@
{
"work": {
"windows": "C:/projects",
"darwin": "/Volumes/path",
"linux": "/mnt/share/projects"
}
}

View file

@ -0,0 +1,44 @@
{
"Generic": {
"short_name": "gener"
},
"Art": {
"short_name": "art"
},
"Modeling": {
"short_name": "mdl"
},
"Texture": {
"short_name": "tex"
},
"Lookdev": {
"short_name": "look"
},
"Rigging": {
"short_name": "rig"
},
"Edit": {
"short_name": "edit"
},
"Layout": {
"short_name": "lay"
},
"Setdress": {
"short_name": "dress"
},
"Animation": {
"short_name": "anim"
},
"FX": {
"short_name": "fx"
},
"Lighting": {
"short_name": "lgt"
},
"Paint": {
"short_name": "paint"
},
"Compositing": {
"short_name": "comp"
}
}

View file

@ -0,0 +1,31 @@
{
"defaults": {
"version_padding": 3,
"version": "v{version:0>{@version_padding}}",
"frame_padding": 4,
"frame": "{frame:0>{@frame_padding}}"
},
"work": {
"folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/work/{task}",
"file": "{project[code]}_{asset}_{task}_{@version}<_{comment}>.{ext}",
"path": "{@folder}/{@file}"
},
"render": {
"folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/render/{subset}/{@version}",
"file": "{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{ext}",
"path": "{@folder}/{@file}"
},
"publish": {
"folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}",
"file": "{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{ext}",
"path": "{@folder}/{@file}",
"thumbnail": "{thumbnail_root}/{project[name]}/{_id}_{thumbnail_type}.{ext}"
},
"hero": {
"folder": "{root[work]}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/hero",
"file": "{project[code]}_{asset}_{subset}_hero<_{output}><.{frame}>.{ext}",
"path": "{@folder}/{@file}"
},
"delivery": {},
"others": {}
}

View file

@ -0,0 +1,13 @@
{
"publish": {
"ExtractCelactionDeadline": {
"enabled": true,
"deadline_department": "",
"deadline_priority": 50,
"deadline_pool": "",
"deadline_pool_secondary": "",
"deadline_group": "",
"deadline_chunk_size": 10
}
}
}

View file

@ -0,0 +1,51 @@
{
"publish": {
"MayaSubmitDeadline": {
"enabled": true,
"optional": false,
"active": true,
"tile_assembler_plugin": "oiio",
"use_published": true,
"asset_dependencies": true,
"group": "none",
"limit": []
},
"NukeSubmitDeadline": {
"enabled": true,
"optional": false,
"active": true,
"use_published": true,
"priority": 50,
"chunk_size": 10,
"primary_pool": "",
"secondary_pool": "",
"group": "",
"department": "",
"limit_groups": {}
},
"HarmonySubmitDeadline": {
"enabled": true,
"optional": false,
"active": true,
"use_published": true,
"priority": 50,
"chunk_size": 10000,
"primary_pool": "",
"secondary_pool": "",
"group": "",
"department": ""
},
"AfterEffectsSubmitDeadline": {
"enabled": true,
"optional": false,
"active": true,
"use_published": true,
"priority": 50,
"chunk_size": 10000,
"primary_pool": "",
"secondary_pool": "",
"group": "",
"department": ""
}
}
}

View file

@ -0,0 +1,206 @@
{
"events": {
"sync_to_avalon": {
"enabled": true,
"statuses_name_change": [
"ready",
"not ready"
]
},
"sync_hier_entity_attributes": {
"enabled": true,
"interest_entity_types": [
"Shot",
"Asset Build"
],
"interest_attributes": [
"frameStart",
"frameEnd"
],
"action_enabled": true,
"role_list": [
"Pypeclub",
"Administrator",
"Project Manager"
]
},
"clone_review_session": {
"enabled": true,
"role_list": [
"Pypeclub",
"Administrator",
"Project Manager"
]
},
"thumbnail_updates": {
"enabled": true,
"levels": 1
},
"user_assignment": {
"enabled": true
},
"status_update": {
"enabled": true,
"mapping": {
"In Progress": [
"__any__"
],
"Ready": [
"Not Ready"
],
"__ignore__": [
"in prgoress",
"omitted",
"on hold"
]
}
},
"status_task_to_parent": {
"enabled": true,
"parent_object_types": [
"Shot",
"Asset Build"
],
"parent_status_match_all_task_statuses": {
"Completed": [
"Approved",
"Omitted"
]
},
"parent_status_by_task_status": [
{
"new_status": "In Progress",
"task_statuses": [
"in progress",
"change requested",
"retake",
"pending review"
]
}
]
},
"status_task_to_version": {
"enabled": true,
"mapping": {},
"asset_types_filter": []
},
"status_version_to_task": {
"enabled": true,
"mapping": {},
"asset_types_to_skip": []
},
"first_version_status": {
"enabled": true,
"status": ""
},
"next_task_update": {
"enabled": true,
"mapping": {
"Not Ready": "Ready"
},
"ignored_statuses": [
"Omitted"
],
"name_sorting": false
}
},
"user_handlers": {
"application_launch_statuses": {
"enabled": true,
"ignored_statuses": [
"In Progress",
"Omitted",
"On hold",
"Approved"
],
"status_change": {
"In Progress": []
}
},
"create_update_attributes": {
"role_list": [
"Pypeclub",
"Administrator"
]
},
"prepare_project": {
"enabled": true,
"role_list": [
"Pypeclub",
"Administrator",
"Project manager"
]
},
"clean_hierarchical_attr": {
"enabled": true,
"role_list": [
"Pypeclub",
"Administrator",
"Project manager"
]
},
"delete_asset_subset": {
"enabled": true,
"role_list": [
"Pypeclub",
"Administrator",
"Project Manager"
]
},
"delete_old_versions": {
"enabled": true,
"role_list": [
"Pypeclub",
"Project Manager",
"Administrator"
]
},
"delivery_action": {
"enabled": true,
"role_list": [
"Pypeclub",
"Project Manager",
"Administrator"
]
},
"store_thubmnail_to_avalon": {
"enabled": true,
"role_list": [
"Pypeclub",
"Project Manager",
"Administrator"
]
},
"job_killer": {
"enabled": true,
"role_list": [
"Pypeclub",
"Administrator"
]
},
"sync_to_avalon_local": {
"enabled": true,
"role_list": [
"Pypeclub",
"Administrator"
]
},
"seed_project": {
"enabled": true,
"role_list": [
"Pypeclub"
]
}
},
"publish": {
"IntegrateFtrackNote": {
"enabled": true,
"note_with_intent_template": "",
"note_labels": []
},
"ValidateFtrackAttributes": {
"enabled": false,
"ftrack_custom_attributes": {}
}
}
}

View file

@ -0,0 +1,224 @@
{
"publish": {
"IntegrateHeroVersion": {
"enabled": true
},
"ExtractJpegEXR": {
"enabled": true,
"ffmpeg_args": {
"input": [],
"output": []
}
},
"ExtractReview": {
"enabled": true,
"profiles": [
{
"families": [],
"hosts": [],
"outputs": {
"h264": {
"ext": "mp4",
"tags": [
"burnin",
"ftrackreview"
],
"ffmpeg_args": {
"video_filters": [],
"audio_filters": [],
"input": [
"-gamma 2.2"
],
"output": [
"-pix_fmt yuv420p",
"-crf 18",
"-intra"
]
},
"filter": {
"families": [
"render",
"review",
"ftrack"
]
},
"width": 0,
"height": 0
}
}
}
]
},
"ExtractBurnin": {
"enabled": true,
"options": {
"font_size": 42,
"opacity": 1.0,
"bg_opacity": 0.5,
"x_offset": 5,
"y_offset": 5,
"bg_padding": 5
},
"profiles": [
{
"families": [],
"hosts": [],
"burnins": {
"burnin": {
"TOP_LEFT": "{yy}-{mm}-{dd}",
"TOP_CENTERED": "",
"TOP_RIGHT": "{anatomy[version]}",
"BOTTOM_LEFT": "{username}",
"BOTTOM_CENTERED": "{asset}",
"BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}"
}
}
}
]
},
"IntegrateAssetNew": {
"template_name_profiles": {
"publish": {
"families": [],
"tasks": []
},
"render": {
"families": [
"review",
"render",
"prerender"
]
}
}
},
"ProcessSubmittedJobOnFarm": {
"enabled": true,
"deadline_department": "",
"deadline_pool": "",
"deadline_group": "",
"deadline_chunk_size": 1,
"deadline_priority": 50,
"aov_filter": {
"maya": [
".+(?:\\.|_)([Bb]eauty)(?:\\.|_).*"
],
"nuke": [],
"aftereffects": [
".*"
],
"celaction": [
".*"
]
}
}
},
"tools": {
"creator": {
"families_smart_select": {
"Render": [
"light",
"render"
],
"Model": [
"model"
],
"Layout": [
"layout"
],
"Look": [
"look"
],
"Rig": [
"rigging",
"rig"
]
},
"subset_name_profiles": [
{
"families": [],
"hosts": [],
"tasks": [],
"template": "{family}{Variant}"
},
{
"families": [
"render"
],
"hosts": [],
"tasks": [],
"template": "{family}{Task}{Variant}"
}
]
},
"Workfiles": {
"last_workfile_on_startup": [
{
"hosts": [],
"tasks": [],
"enabled": true
}
],
"sw_folders": {
"compositing": [
"nuke",
"ae"
],
"modeling": [
"maya",
"blender",
"zbrush"
],
"lookdev": [
"substance",
"textures"
]
}
}
},
"project_folder_structure": {
"__project_root__": {
"prod": {},
"resources": {
"footage": {
"plates": {},
"offline": {}
},
"audio": {},
"art_dept": {}
},
"editorial": {},
"assets[ftrack.Library]": {
"characters[ftrack]": {},
"locations[ftrack]": {}
},
"shots[ftrack.Sequence]": {
"scripts": {},
"editorial[ftrack.Folder]": {}
}
}
},
"sync_server": {
"enabled": true,
"config": {
"retry_cnt": "3",
"loop_delay": "60",
"active_site": "studio",
"remote_site": "studio"
},
"sites": {
"gdrive": {
"provider": "gdrive",
"credentials_url": "",
"root": {
"work": ""
}
}
}
},
"project_plugins": {
"windows": [],
"darwin": [],
"linux": []
},
"project_environments": {}
}

View file

@ -0,0 +1,20 @@
{
"general": {
"skip_resolution_check": [],
"skip_timelines_check": []
},
"publish": {
"CollectPalettes": {
"allowed_tasks": [
"."
]
},
"HarmonySubmitDeadline": {
"use_published": false,
"priority": 50,
"primary_pool": "",
"secondary_pool": "",
"chunk_size": 0
}
}
}

View file

@ -0,0 +1,32 @@
{
"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
}
},
"publish": {
"CollectInstanceVersion": {
"enabled": false
},
"ExtractReviewCutUpVideo": {
"enabled": true,
"tags_addition": [
"review"
]
}
},
"filters": {}
}

View file

@ -0,0 +1,562 @@
{
"ext_mapping": {
"model": "ma",
"mayaAscii": "ma",
"camera": "ma",
"rig": "ma",
"workfile": "ma",
"yetiRig": "ma"
},
"create": {
"CreateAnimation": {
"enabled": true,
"defaults": [
"Main"
]
},
"CreateAss": {
"enabled": true,
"defaults": [
"Main"
]
},
"CreateAssembly": {
"enabled": true,
"defaults": [
"Main"
]
},
"CreateCamera": {
"enabled": true,
"defaults": [
"Main"
]
},
"CreateLayout": {
"enabled": true,
"defaults": [
"Main"
]
},
"CreateLook": {
"enabled": true,
"defaults": [
"Main"
]
},
"CreateMayaScene": {
"enabled": true,
"defaults": [
"Main"
]
},
"CreateModel": {
"enabled": true,
"defaults": [
"Main",
"Proxy",
"Sculpt"
]
},
"CreatePointCache": {
"enabled": true,
"defaults": [
"Main"
]
},
"CreateRender": {
"enabled": true,
"defaults": [
"Main"
]
},
"CreateRenderSetup": {
"enabled": true,
"defaults": [
"Main"
]
},
"CreateReview": {
"enabled": true,
"defaults": [
"Main"
]
},
"CreateRig": {
"enabled": true,
"defaults": [
"Main",
"Sim",
"Cloth"
]
},
"CreateSetDress": {
"enabled": true,
"defaults": [
"Main",
"Anim"
]
},
"CreateUnrealStaticMesh": {
"enabled": true,
"defaults": [
"Main"
]
},
"CreateVrayProxy": {
"enabled": true,
"defaults": [
"Main"
]
},
"CreateVRayScene": {
"enabled": true,
"defaults": [
"Main"
]
},
"CreateYetiRig": {
"enabled": true,
"defaults": [
"Main"
]
}
},
"publish": {
"CollectMayaRender": {
"sync_workfile_version": false
},
"ValidateShaderName": {
"enabled": false,
"regex": "(?P<asset>.*)_(.*)_SHD"
},
"ValidateAttributes": {
"enabled": false,
"attributes": {}
},
"ValidateModelName": {
"enabled": false,
"material_file": {
"windows": "",
"darwin": "",
"linux": ""
},
"regex": "(.*)_(\\\\d)*_(.*)_(GEO)"
},
"ValidateTransformNamingSuffix": {
"enabled": true,
"SUFFIX_NAMING_TABLE": {
"mesh": [
"_GEO",
"_GES",
"_GEP",
"_OSD"
],
"nurbsCurve": [
"_CRV"
],
"nurbsSurface": [
"_NRB"
],
"locator": [
"_LOC"
],
"group": [
"_GRP"
]
},
"ALLOW_IF_NOT_IN_SUFFIX_TABLE": true
},
"ValidateColorSets": {
"enabled": false,
"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
},
"ValidateMeshNonManifold": {
"enabled": false,
"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": true,
"active": true
},
"ValidateNoNullTransforms": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateNoUnknownNodes": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateNodeNoGhosting": {
"enabled": false,
"optional": true,
"active": true
},
"ValidateShapeDefaultNames": {
"enabled": false,
"optional": true,
"active": true
},
"ValidateShapeRenderStats": {
"enabled": false,
"optional": true,
"active": true
},
"ValidateTransformZero": {
"enabled": false,
"optional": true,
"active": true
},
"ValidateCameraAttributes": {
"enabled": false,
"optional": true,
"active": true
},
"ValidateAssemblyName": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateAssRelativePaths": {
"enabled": true,
"optional": true,
"active": true
},
"ExtractPlayblast": {
"capture_preset": {
"Codec": {
"compression": "jpg",
"format": "image",
"quality": 95
},
"Display Options": {
"background": [
0.7,
0.7,
0.7
],
"backgroundBottom": [
0.7,
0.7,
0.7
],
"backgroundTop": [
0.7,
0.7,
0.7
],
"override_display": true
},
"Generic": {
"isolate_view": true,
"off_screen": true
},
"PanZoom": {
"pan_zoom": true
},
"Renderer": {
"rendererName": "vp2Renderer"
},
"Resolution": {
"width": 1080,
"height": 1920,
"percent": 1.0,
"mode": "Custom"
},
"Viewport Options": {
"override_viewport_options": true,
"displayLights": "default",
"textureMaxResolution": 1024,
"multiSample": 4,
"shadows": true,
"textures": true,
"twoSidedLighting": true,
"ssaoEnable": true,
"cameras": false,
"clipGhosts": false,
"controlVertices": false,
"deformers": false,
"dimensions": false,
"dynamicConstraints": false,
"dynamics": false,
"fluids": false,
"follicles": false,
"gpuCacheDisplayFilter": false,
"greasePencils": false,
"grid": false,
"hairSystems": true,
"handles": false,
"hud": false,
"hulls": false,
"ikHandles": false,
"imagePlane": true,
"joints": false,
"lights": false,
"locators": false,
"manipulators": false,
"motionTrails": false,
"nCloths": false,
"nParticles": false,
"nRigids": false,
"nurbsCurves": false,
"nurbsSurfaces": false,
"particleInstancers": false,
"pivots": false,
"planes": false,
"pluginShapes": false,
"polymeshes": true,
"strokes": false,
"subdivSurfaces": false
},
"Camera Options": {
"displayGateMask": false,
"displayResolution": false,
"displayFilmGate": false,
"displayFieldChart": false,
"displaySafeAction": false,
"displaySafeTitle": false,
"displayFilmPivot": false,
"displayFilmOrigin": false,
"overscan": 1.0
}
}
},
"ExtractCameraAlembic": {
"enabled": true,
"optional": true,
"active": true,
"bake_attributes": []
},
"MayaSubmitDeadline": {
"enabled": true,
"tile_assembler_plugin": "DraftTileAssembler"
}
},
"load": {
"colors": {
"model": [
0.821,
0.518,
0.117
],
"rig": [
0.144,
0.443,
0.463
],
"pointcache": [
0.368,
0.821,
0.117
],
"animation": [
0.368,
0.821,
0.117
],
"ass": [
1.0,
0.332,
0.312
],
"camera": [
0.447,
0.312,
1.0
],
"fbx": [
1.0,
0.931,
0.312
],
"mayaAscii": [
0.312,
1.0,
0.747
],
"setdress": [
0.312,
1.0,
0.747
],
"layout": [
0.312,
1.0,
0.747
],
"vdbcache": [
0.312,
1.0,
0.428
],
"vrayproxy": [
0.258,
0.95,
0.541
],
"yeticache": [
0.2,
0.8,
0.3
],
"yetiRig": [
0.0,
0.8,
0.5
]
}
},
"workfile_build": {
"profiles": [
{
"tasks": [
"Lighting"
],
"current_context": [
{
"subset_name_filters": [
"\".+[Mm]ain\""
],
"families": [
"model"
],
"repre_names": [
"abc",
"ma"
],
"loaders": [
"ReferenceLoader"
]
},
{
"subset_name_filters": [],
"families": [
"animation",
"pointcache"
],
"repre_names": [
"abc"
],
"loaders": [
"ReferenceLoader"
]
},
{
"subset_name_filters": [],
"families": [
"rendersetup"
],
"repre_names": [
"json"
],
"loaders": [
"RenderSetupLoader"
]
},
{
"subset_name_filters": [],
"families": [
"camera"
],
"repre_names": [
"abc"
],
"loaders": [
"ReferenceLoader"
]
}
],
"linked_assets": [
{
"subset_name_filters": [],
"families": [
"sedress"
],
"repre_names": [
"ma"
],
"loaders": [
"ReferenceLoader"
]
},
{
"subset_name_filters": [],
"families": [
"ArnoldStandin"
],
"repre_names": [
"ass"
],
"loaders": [
"assLoader"
]
}
]
}
]
},
"filters": {
"preset 1": {
"ValidateNoAnimation": false,
"ValidateShapeDefaultNames": false
},
"preset 2": {
"ValidateNoAnimation": false
}
}
}

View file

@ -0,0 +1,181 @@
{
"general": {
"menu": {
"create": "ctrl+shift+alt+c",
"publish": "ctrl+alt+p",
"load": "ctrl+alt+l",
"manage": "ctrl+alt+m",
"build_workfile": "ctrl+alt+b"
}
},
"create": {
"CreateWriteRender": {
"fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}"
},
"CreateWritePrerender": {
"fpath_template": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}"
}
},
"publish": {
"PreCollectNukeInstances": {
"sync_workfile_version": true
},
"ValidateKnobs": {
"enabled": false,
"knobs": {
"render": {
"review": true
}
}
},
"ValidateOutputResolution": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateGizmo": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateScript": {
"enabled": true,
"optional": true,
"active": true
},
"ValidateNukeWriteBoundingBox": {
"enabled": true,
"optional": true,
"active": true
},
"ExtractThumbnail": {
"enabled": true,
"nodes": {
"Reformat": [
[
"type",
"to format"
],
[
"format",
"HD_1080"
],
[
"filter",
"Lanczos6"
],
[
"black_outside",
true
],
[
"pbb",
false
]
]
}
},
"ExtractReviewDataLut": {
"enabled": false
},
"ExtractReviewDataMov": {
"enabled": true,
"viewer_lut_raw": false
},
"ExtractSlateFrame": {
"viewer_lut_raw": false
},
"NukeSubmitDeadline": {
"deadline_priority": 50,
"deadline_pool": "",
"deadline_pool_secondary": "",
"deadline_chunk_size": 1
}
},
"load": {
"LoadImage": {
"enabled": true,
"families": [
"render2d",
"source",
"plate",
"render",
"prerender",
"review",
"image"
],
"representations": [
"exr",
"dpx",
"jpg",
"jpeg",
"png",
"psd"
],
"node_name_template": "{class_name}_{ext}"
},
"LoadMov": {
"enabled": true,
"families": [
"source",
"plate",
"render",
"prerender",
"review"
],
"representations": [
"mov",
"review",
"mp4",
"h264"
],
"node_name_template": "{class_name}_{ext}"
},
"LoadSequence": {
"enabled": true,
"families": [
"render2d",
"source",
"plate",
"render",
"prerender",
"review"
],
"representations": [
"exr",
"dpx",
"jpg",
"jpeg",
"png"
],
"node_name_template": "{class_name}_{ext}"
}
},
"workfile_build": {
"profiles": [
{
"tasks": [
"compositing"
],
"current_context": [
{
"subset_name_filters": [],
"families": [
"render",
"plate"
],
"repre_names": [
"exr",
"dpx"
],
"loaders": [
"LoadSequence"
]
}
],
"linked_assets": []
}
]
},
"filters": {}
}

View file

@ -0,0 +1,20 @@
{
"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
}
}
}

View file

@ -0,0 +1,125 @@
{
"create": {
"create_workfile": {
"name": "workfile",
"label": "Workfile",
"family": "workfile",
"icon": "cube",
"defaults": [
"Main"
],
"help": "Working scene backup"
},
"create_model": {
"name": "model",
"label": "Model",
"family": "model",
"icon": "cube",
"defaults": [
"Main"
],
"help": "Polygonal static geometry"
},
"create_rig": {
"name": "rig",
"label": "Rig",
"family": "rig",
"icon": "wheelchair",
"defaults": [
"Main",
"Cloth"
],
"help": "Artist-friendly rig with controls"
},
"create_pointcache": {
"name": "pointcache",
"label": "Pointcache",
"family": "pointcache",
"icon": "gears",
"defaults": [
"Main"
],
"help": "Alembic pointcache for animated data"
},
"create_plate": {
"name": "plate",
"label": "Plate",
"family": "plate",
"icon": "camera",
"defaults": [
"Main",
"BG",
"Animatic",
"Reference",
"Offline"
],
"help": "Footage for composting or reference"
},
"create_camera": {
"name": "camera",
"label": "Camera",
"family": "camera",
"icon": "camera",
"defaults": [
"Main"
],
"help": "video-camera"
},
"create_editorial": {
"name": "editorial",
"label": "Editorial",
"family": "editorial",
"icon": "image",
"defaults": [
"Main"
],
"help": "Editorial files to generate shots."
},
"create_image": {
"name": "image",
"label": "Image file",
"family": "image",
"icon": "image",
"defaults": [
"Reference",
"Texture",
"ConceptArt",
"MattePaint"
],
"help": "Holder for all kinds of image data"
},
"create_matchmove": {
"name": "matchmove",
"label": "Matchmove Scripts",
"family": "matchmove",
"icon": "empire",
"defaults": [
"Camera",
"Object",
"Mocap"
],
"help": "Script exported from matchmoving application"
},
"__dynamic_keys_labels__": {
"create_workfile": "Workfile",
"create_model": "Model",
"create_rig": "Rig",
"create_pointcache": "Pointcache",
"create_plate": "Plate",
"create_camera": "Camera",
"create_editorial": "Editorial",
"create_image": "Image",
"create_matchmove": "Matchmove"
}
},
"publish": {
"ExtractThumbnailSP": {
"ffmpeg_args": {
"input": [
"gamma 2.2"
],
"output": []
}
}
}
}

View file

@ -0,0 +1,6 @@
{
"project_setup": {
"dev_mode": true,
"install_unreal_python_engine": false
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,23 @@
{
"studio_name": "Studio name",
"studio_code": "stu",
"environment": {
"FFMPEG_PATH": {
"windows": "{OPENPYPE_ROOT}/vendor/bin/ffmpeg_exec/windows/bin",
"darwin": "{OPENPYPE_ROOT}/vendor/bin/ffmpeg_exec/darwin/bin",
"linux": ":{OPENPYPE_ROOT}/vendor/bin/ffmpeg_exec/linux"
},
"OPENPYPE_OCIO_CONFIG": "{STUDIO_SOFT}/OpenColorIO-Configs",
"__environment_keys__": {
"global": [
"FFMPEG_PATH",
"OPENPYPE_OCIO_CONFIG"
]
}
},
"openpype_path": {
"windows": [],
"darwin": [],
"linux": []
}
}

View file

@ -0,0 +1,172 @@
{
"avalon": {
"AVALON_TIMEOUT": 1000,
"AVALON_THUMBNAIL_ROOT": {
"windows": "",
"darwin": "",
"linux": ""
}
},
"ftrack": {
"enabled": true,
"ftrack_server": "",
"ftrack_actions_path": [],
"ftrack_events_path": [],
"intent": {
"items": {
"-": "-",
"wip": "WIP",
"final": "Final",
"test": "Test"
},
"default": "-"
},
"custom_attributes": {
"show": {
"avalon_auto_sync": {
"write_security_roles": [
"API",
"Administrator"
],
"read_security_roles": [
"API",
"Administrator"
]
},
"library_project": {
"write_security_roles": [
"API",
"Administrator"
],
"read_security_roles": [
"API",
"Administrator"
]
},
"applications": {
"write_security_roles": [
"API",
"Administrator",
"Pypeclub"
],
"read_security_roles": [
"API",
"Administrator",
"Pypeclub"
]
}
},
"is_hierarchical": {
"tools_env": {
"write_security_roles": [
"API",
"Administrator",
"Pypeclub"
],
"read_security_roles": [
"API",
"Administrator",
"Pypeclub"
]
},
"avalon_mongo_id": {
"write_security_roles": [
"API",
"Administrator",
"Pypeclub"
],
"read_security_roles": [
"API",
"Administrator",
"Pypeclub"
]
},
"fps": {
"write_security_roles": [],
"read_security_roles": []
},
"frameStart": {
"write_security_roles": [],
"read_security_roles": []
},
"frameEnd": {
"write_security_roles": [],
"read_security_roles": []
},
"clipIn": {
"write_security_roles": [],
"read_security_roles": []
},
"clipOut": {
"write_security_roles": [],
"read_security_roles": []
},
"handleStart": {
"write_security_roles": [],
"read_security_roles": []
},
"handleEnd": {
"write_security_roles": [],
"read_security_roles": []
},
"resolutionWidth": {
"write_security_roles": [],
"read_security_roles": []
},
"resolutionHeight": {
"write_security_roles": [],
"read_security_roles": []
},
"pixelAspect": {
"write_security_roles": [],
"read_security_roles": []
}
}
}
},
"timers_manager": {
"enabled": true,
"full_time": 15.0,
"message_time": 0.5
},
"clockify": {
"enabled": false,
"workspace_name": ""
},
"sync_server": {
"enabled": false
},
"deadline": {
"enabled": true,
"DEADLINE_REST_URL": "http://localhost:8082"
},
"muster": {
"enabled": false,
"MUSTER_REST_URL": "http://127.0.0.1:9890",
"templates_mapping": {
"file_layers": 7,
"mentalray": 2,
"mentalray_sf": 6,
"redshift": 55,
"renderman": 29,
"software": 1,
"software_sf": 5,
"turtle": 10,
"vector": 4,
"vray": 37,
"ffmpeg": 48
}
},
"log_viewer": {
"enabled": true
},
"user": {
"enabled": true
},
"standalonepublish_tool": {
"enabled": true
},
"idle_manager": {
"enabled": true
}
}

View file

@ -0,0 +1,53 @@
{
"tool_groups": {
"mtoa": {
"environment": {
"MTOA": "{STUDIO_SOFTWARE}/arnold/mtoa_{MAYA_VERSION}_{MTOA_VERSION}",
"MAYA_RENDER_DESC_PATH": "{MTOA}",
"MAYA_MODULE_PATH": "{MTOA}",
"ARNOLD_PLUGIN_PATH": "{MTOA}/shaders",
"MTOA_EXTENSIONS_PATH": {
"darwin": "{MTOA}/extensions",
"linux": "{MTOA}/extensions",
"windows": "{MTOA}/extensions"
},
"MTOA_EXTENSIONS": {
"darwin": "{MTOA}/extensions",
"linux": "{MTOA}/extensions",
"windows": "{MTOA}/extensions"
},
"DYLD_LIBRARY_PATH": {
"darwin": "{MTOA}/bin"
},
"PATH": {
"windows": "{PATH};{MTOA}/bin"
}
},
"variants": {
"3-2": {
"MTOA_VERSION": "3.2"
},
"3-1": {
"MTOA_VERSION": "3.1"
},
"__dynamic_keys_labels__": {
"3-2": "3.2",
"3-1": "3.2"
}
}
},
"vray": {
"environment": {},
"variants": {}
},
"yeti": {
"environment": {},
"variants": {}
},
"__dynamic_keys_labels__": {
"mtoa": "Autodesk Arnold",
"vray": "Chaos Group Vray",
"yeti": "Pergrine Labs Yeti"
}
}
}

View file

@ -0,0 +1,160 @@
"""OpenPype Settings
Settings define how openpype and it's modules behave. They became main
component of dynamism.
OpenPype settings (ATM) have 3 layers:
1.) Defaults - defined in code
2.) Studio overrides - values that are applied on default that may modify only
some values or None, result can be called "studio settings"
3.) Project overrides - values that are applied on studio settings, may modify
some values or None and may modify values that are not modified in studio
overrides
To be able do these overrides it is required to store metadata defying which
data are applied and how. Because of that it is not possible to modify
overrides manually and expect it would work right.
Structure of settings is defined with schemas. Schemas have defined structure
and possible types with possible attributes (Schemas and their description
can be found in "./schemas/README.md").
To modify settings it's recommended to use UI settings tool which can easily
visuallise how values are applied.
With help of setting entities it is possible to modify settings from code.
OpenPype has (ATM) 2 types of settings:
1.) System settings - global system settings, don't have project overrides
2.) Project settings - project specific settings
Startpoint is root entity that cares about access to other setting entities
in their scope. To be able work with entities it is required to understand
setting schemas and their structure. It is possible to work with dictionary
and list entities as with standard python objects.
```python
# Create an object of system settings.
system_settings = SystemSettings()
# How to get value of entity
print(system_settings["general"]["studio_name"].value)
>>> TestStudio Name
# How to set value
# Variant 1
system_settings["general"]["studio_name"] = "StudioidutS"
# Variant 2
system_settings["general"]["studio_name"].set("StudioidutS")
print(system_settings["general"]["studio_name"].value)
>>> StudioidutS
```
"""
from .exceptions import (
SchemaError,
DefaultsNotDefined,
StudioDefaultsNotDefined,
InvalidValueType,
InvalidKeySymbols,
SchemaMissingFileInfo,
SchemeGroupHierarchyBug,
SchemaDuplicatedKeys,
SchemaDuplicatedEnvGroupKeys,
SchemaTemplateMissingKeys
)
from .lib import (
NOT_SET,
OverrideState
)
from .base_entity import (
BaseEntity,
GUIEntity,
BaseItemEntity,
ItemEntity
)
from .root_entities import (
SystemSettings,
ProjectSettings
)
from .item_entities import (
PathEntity,
ListStrictEntity
)
from .input_entities import (
EndpointEntity,
InputEntity,
NumberEntity,
BoolEntity,
TextEntity,
PathInput,
RawJsonEntity
)
from .enum_entity import (
BaseEnumEntity,
EnumEntity,
AppsEnumEntity,
ToolsEnumEntity
)
from .list_entity import ListEntity
from .dict_immutable_keys_entity import DictImmutableKeysEntity
from .dict_mutable_keys_entity import DictMutableKeysEntity
from .anatomy_entities import AnatomyEntity
__all__ = (
"DefaultsNotDefined",
"StudioDefaultsNotDefined",
"InvalidValueType",
"InvalidKeySymbols",
"SchemaMissingFileInfo",
"SchemeGroupHierarchyBug",
"SchemaDuplicatedKeys",
"SchemaDuplicatedEnvGroupKeys",
"SchemaTemplateMissingKeys",
"NOT_SET",
"OverrideState",
"BaseEntity",
"GUIEntity",
"BaseItemEntity",
"ItemEntity",
"SystemSettings",
"ProjectSettings",
"PathEntity",
"ListStrictEntity",
"EndpointEntity",
"InputEntity",
"NumberEntity",
"BoolEntity",
"TextEntity",
"PathInput",
"RawJsonEntity",
"BaseEnumEntity",
"EnumEntity",
"AppsEnumEntity",
"ToolsEnumEntity",
"ListEntity",
"DictImmutableKeysEntity",
"DictMutableKeysEntity",
"AnatomyEntity"
)

View file

@ -0,0 +1,25 @@
from .dict_immutable_keys_entity import DictImmutableKeysEntity
from .lib import OverrideState
class AnatomyEntity(DictImmutableKeysEntity):
schema_types = ["anatomy"]
def _update_current_metadata(self):
if self._override_state is OverrideState.PROJECT:
return {}
return super(AnatomyEntity, self)._update_current_metadata()
def set_override_state(self, *args, **kwargs):
super(AnatomyEntity, self).set_override_state(*args, **kwargs)
if self._override_state is OverrideState.PROJECT:
for child_obj in self.non_gui_children.values():
if not child_obj.has_project_override:
self.add_to_project_override()
break
def on_child_change(self, child_obj):
if self._override_state is OverrideState.PROJECT:
if not child_obj.has_project_override:
child_obj.add_to_project_override()
return super(AnatomyEntity, self).on_child_change(child_obj)

View file

@ -0,0 +1,874 @@
from uuid import uuid4
from abc import ABCMeta, abstractmethod, abstractproperty
import six
from .lib import (
NOT_SET,
OverrideState
)
from .exceptions import (
InvalidValueType,
SchemeGroupHierarchyBug,
EntitySchemaError
)
from openpype.lib import PypeLogger
@six.add_metaclass(ABCMeta)
class BaseEntity:
"""Base entity class for Setting's item type workflow.
Args:
schema_data (dict): Schema data that defines entity behavior.
"""
def __init__(self, schema_data, *args, **kwargs):
self.schema_data = schema_data
# Entity id
self._id = uuid4()
def __hash__(self):
"""Make entity hashable by it's id.
Helps to store entities as keys in dictionary.
"""
return self.id
@property
def id(self):
"""Unified identifier of an entity."""
return self._id
@abstractproperty
def gui_type(self):
"""Is entity GUI type entity."""
pass
@abstractmethod
def schema_validations(self):
"""Validation of schema."""
pass
class GUIEntity(BaseEntity):
"""Entity without any specific logic that should be handled only in GUI."""
gui_type = True
schema_types = ["separator", "splitter", "label"]
def __getitem__(self, key):
return self.schema_data[key]
def schema_validations(self):
"""TODO validate GUI schemas."""
pass
class BaseItemEntity(BaseEntity):
"""Base of item entity that is not only for GUI but can modify values.
Defines minimum attributes of all entities that are not `gui_type`.
Args:
schema_data (dict): Schema data that defines entity behavior.
"""
gui_type = False
def __init__(self, schema_data):
super(BaseItemEntity, self).__init__(schema_data)
# Parent entity
self.parent = None
# Entity is dynamically created (in list or dict with mutable keys)
# - can be also dynamically removed
self.is_dynamic_item = False
# Log object created on demand with `log` attribute
self._log = None
# Item path attribute (may be filled or be dynamic)
self._path = None
# These should be set on initialization and not change then
self.valid_value_types = getattr(self, "valid_value_types", NOT_SET)
self.value_on_not_set = getattr(self, "value_on_not_set", NOT_SET)
# Entity represents group entity
# - all children entities will be saved on modification of overrides
self.is_group = False
# Entity's value will be stored into file with name of it's key
self.is_file = False
# Reference to parent entity which has `is_group` == True
# - stays as None if none of parents is group
self.group_item = None
# Reference to parent entity which has `is_file` == True
self.file_item = None
# Reference to `RootEntity`
self.root_item = None
# Entity is in hierarchy of dynamically created entity
self.is_in_dynamic_item = False
# Entity will save metadata about environments
# - this is current possible only for RawJsonEnity
self.is_env_group = False
# Key of environment group key must be unique across system settings
self.env_group_key = None
# Roles of an entity
self.roles = None
# Key must be specified in schema data otherwise won't work as expected
self.require_key = True
# Key and label of an entity
self.key = None
self.label = None
# Override state defines which values are used, saved and how.
# TODO convert to private attribute
self._override_state = OverrideState.NOT_DEFINED
# These attributes may change values during existence of an object
# Default value, studio override values and project override values
# - these should be set only with `update_default_value` etc.
# TODO convert to private attributes
self._default_value = NOT_SET
self._studio_override_value = NOT_SET
self._project_override_value = NOT_SET
# Entity has set `_default_value` (is not NOT_SET)
self.has_default_value = False
# Entity is marked as it contain studio override data so it's value
# will be stored to studio overrides. This is relevant attribute
# only if current override state is set to STUDIO.
self._has_studio_override = False
# Entity has set `_studio_override_value` (is not NOT_SET)
self.had_studio_override = False
# Entity is marked as it contain project override data so it's value
# will be stored to project overrides. This is relevant attribute
# only if current override state is set to PROJECT.
self._has_project_override = False
# Entity has set `_project_override_value` (is not NOT_SET)
self.had_project_override = False
# Callbacks that are called on change.
# - main current purspose is to register GUI callbacks
self.on_change_callbacks = []
roles = schema_data.get("roles")
if roles is None:
roles = []
elif not isinstance(roles, list):
roles = [roles]
self.roles = roles
@property
def has_studio_override(self):
"""Says if entity or it's children has studio overrides."""
if self._override_state >= OverrideState.STUDIO:
return self._has_studio_override
return False
@property
def has_project_override(self):
"""Says if entity or it's children has project overrides."""
if self._override_state >= OverrideState.PROJECT:
return self._has_project_override
return False
@property
def path(self):
"""Full path of an entity in settings hierarchy.
It is not possible to use this attribute during initialization because
initialization happens before entity is added to parent's children.
"""
if self._path is not None:
return self._path
path = self.parent.get_child_path(self)
if not self.is_in_dynamic_item and not self.is_dynamic_item:
self._path = path
return path
@abstractmethod
def get_child_path(self, child_entity):
"""Return path for a direct child entity."""
pass
@abstractmethod
def get_entity_from_path(self, path):
"""Return system settings entity."""
pass
def schema_validations(self):
"""Validate schema of entity and it's hierachy.
Contain default validations same for all entities.
"""
# Entity must have defined valid value types.
if self.valid_value_types is NOT_SET:
raise EntitySchemaError(
self, "Attribute `valid_value_types` is not filled."
)
# Check if entity has defined key when is required.
if self.require_key and not self.key:
error_msg = "Missing \"key\" in schema data. {}".format(
str(self.schema_data).replace("'", '"')
)
raise EntitySchemaError(self, error_msg)
# Group entity must have defined label. (UI specific)
# QUESTION this should not be required?
if not self.label and self.is_group:
raise EntitySchemaError(
self, "Item is set as `is_group` but has empty `label`."
)
# Group item can be only once in on hierarchy branch.
if self.is_group and self.group_item:
raise SchemeGroupHierarchyBug(self)
# Validate that env group entities will be stored into file.
# - env group entities must store metadata which is not possible if
# metadata would be outside of file
if not self.file_item and self.is_env_group:
reason = (
"Environment item is not inside file"
" item so can't store metadata for defaults."
)
raise EntitySchemaError(self, reason)
# Dynamic items must not have defined labels. (UI specific)
if self.label and self.is_dynamic_item:
raise EntitySchemaError(
self, "Item has set label but is used as dynamic item."
)
# Dynamic items or items in dynamic item must not have set `is_group`
if self.is_group and (self.is_dynamic_item or self.is_in_dynamic_item):
raise EntitySchemaError(
self, "Dynamic entity has set `is_group` to true."
)
@abstractmethod
def set_override_state(self, state):
"""Set override state and trigger it on children.
Method discard all changes in hierarchy and use values, metadata
and all kind of values for defined override state. May be used to
apply updated values (default, studio overrides, project overrides).
Should start on root entity and when triggered then must be called on
all entities in hierarchy.
Args:
state (OverrideState): State to which should be data changed.
"""
pass
@abstractmethod
def on_change(self):
"""Trigger change callbacks and tell parent that has changed.
Can be any kind of change. Value has changed, has studio overrides
changed from True to False, etc.
"""
pass
@abstractmethod
def on_child_change(self, child_entity):
"""Triggered by children when they've changed.
Args:
child_entity (BaseItemEntity): Direct child entity that has
changed.
"""
pass
@abstractmethod
def set(self, value):
"""Set value of entity.
Args:
value (Any): Setter of value for this entity.
"""
pass
def is_value_valid_type(self, value):
"""Validate passed value type by entity's defined valid types.
Returns:
bool: True if value is in entity's defined types.
"""
return isinstance(value, self.valid_value_types)
def _validate_value_type(self, value):
"""Validate entered value.
Raises:
InvalidValueType: If value's type is not valid by entity's
definition.
"""
if self.is_value_valid_type(value):
return
raise InvalidValueType(self.valid_value_types, type(value), self.path)
def _convert_to_valid_type(self, value):
"""Private method of entity to convert value.
NOTE: Method is not abstract as more entities won't have implemented
logic inside.
Must return NOT_SET if can't convert the value.
"""
return NOT_SET
def convert_to_valid_type(self, value):
"""Check value type with possibility of conversion to valid.
If entered value has right type than is returned as it is. otherwise
is used privete method of entity to try convert.
Raises:
InvalidValueType: If value's type is not valid by entity's
definition and can't be converted by entity logic.
"""
#
if self.is_value_valid_type(value):
return value
new_value = self._convert_to_valid_type(value)
if new_value is not NOT_SET and self.is_value_valid_type(new_value):
return new_value
raise InvalidValueType(self.valid_value_types, type(value), self.path)
# TODO convert to private method
def _check_update_value(self, value, value_source):
"""Validation of value on update methods.
Update methods update data from currently saved settings so it is
possible to have invalid type mainly during development.
Args:
value (Any): Value that got to update method.
value_source (str): Source update method. Is used for logging and
is not used as part of logic ("default", "studio override",
"project override").
Returns:
Any: Return value itself if has valid type.
NOT_SET: If value has invalid type.
"""
# Nothing to validate if is NOT_SET
if value is NOT_SET:
return value
try:
new_value = self.convert_to_valid_type(value)
except InvalidValueType:
new_value = NOT_SET
if new_value is not NOT_SET:
return new_value
# Warning log about invalid value type.
self.log.warning(
(
"{} Got invalid value type for {} values."
" Expected types: {} | Got Type: {} | Value: \"{}\""
).format(
self.path, value_source,
self.valid_value_types, type(value), str(value)
)
)
return NOT_SET
def available_for_role(self, role_name=None):
"""Is entity valid for role.
Args:
role_name (str): Name of role that will be validated. Entity's
`user_role` attribute is used if not defined.
Returns:
bool: True if is available for role.
"""
if not self.roles:
return True
if role_name is None:
role_name = self.user_role
return role_name in self.roles
@property
def user_role(self):
"""Entity is using user role.
Returns:
str: user role as string.
"""
return self.parent.user_role
@property
def log(self):
"""Auto created logger for debugging or warnings."""
if self._log is None:
self._log = PypeLogger.get_logger(self.__class__.__name__)
return self._log
@abstractproperty
def schema_types(self):
pass
@abstractproperty
def has_unsaved_changes(self):
pass
@abstractmethod
def settings_value(self):
"""Value of an item without key."""
pass
@abstractmethod
def save(self):
"""Save data for current state."""
pass
@abstractmethod
def _item_initalization(self):
"""Entity specific initialization process."""
pass
@abstractproperty
def value(self):
"""Value of entity without metadata."""
pass
@property
def can_discard_changes(self):
"""Result defines if `discard_changes` will be processed.
Also can be used as validation before the method is called.
"""
return self.has_unsaved_changes
@property
def can_add_to_studio_default(self):
"""Result defines if `add_to_studio_default` will be processed.
Also can be used as validation before the method is called.
"""
if self._override_state is not OverrideState.STUDIO:
return False
if self.is_dynamic_item or self.is_in_dynamic_item:
return False
# Skip if entity is under group
if self.group_item:
return False
# Skip if is group and any children is already marked with studio
# overrides
if self.is_group and self.has_studio_override:
return False
return True
@property
def can_remove_from_studio_default(self):
"""Result defines if `remove_from_studio_default` can be triggered.
This can be also used as validation before the method is called.
"""
if self._override_state is not OverrideState.STUDIO:
return False
if self.is_dynamic_item or self.is_in_dynamic_item:
return False
if not self.has_studio_override:
return False
return True
@property
def can_add_to_project_override(self):
"""Result defines if `add_to_project_override` can be triggered.
Also can be used as validation before the method is called.
"""
if self.is_dynamic_item or self.is_in_dynamic_item:
return False
# Show only when project overrides are set
if self._override_state is not OverrideState.PROJECT:
return False
# Do not show on items under group item
if self.group_item:
return False
# Skip if already is marked to save project overrides
if self.is_group and self.has_project_override:
return False
return True
@property
def can_remove_from_project_override(self):
"""Result defines if `remove_from_project_override` can be triggered.
This can be also used as validation before the method is called.
"""
if self.is_dynamic_item or self.is_in_dynamic_item:
return False
if self._override_state is not OverrideState.PROJECT:
return False
# Dynamic items can't have these actions
if self.is_dynamic_item or self.is_in_dynamic_item:
return False
if not self.has_project_override:
return False
return True
def discard_changes(self, on_change_trigger=None):
"""Discard changes on entity and it's children.
Reset all values to same values as had when `set_override_state` was
called last time.
Must not affect `had_studio_override` value or `had_project_override`
value. It must be marked that there are keys/values which are not in
defaults or overrides.
Won't affect if will be stored as overrides if entity is under
group entity in hierarchy.
This is wrapper method that handles on_change callbacks only when all
`_discard_changes` on all children happened. That is important as
value changes may trigger change callbacks that must be ignored.
Callbacks are triggered by entity where method was called.
Args:
on_change_trigger (list): Callbacks of `on_change` should be stored
to trigger them afterwards.
"""
initialized = False
if on_change_trigger is None:
if not self.can_discard_changes:
return
initialized = True
on_change_trigger = []
self._discard_changes(on_change_trigger)
if initialized:
for callback in on_change_trigger:
callback()
@abstractmethod
def _discard_changes(self, on_change_trigger):
"""Entity's implementation to discard all changes made by user."""
pass
def add_to_studio_default(self, on_change_trigger=None):
initialized = False
if on_change_trigger is None:
if not self.can_add_to_studio_default:
return
initialized = True
on_change_trigger = []
self._add_to_studio_default(on_change_trigger)
if initialized:
for callback in on_change_trigger:
callback()
@abstractmethod
def _add_to_studio_default(self, on_change_trigger):
"""Item's implementation to set current values as studio's overrides.
Mark item and it's children as they have studio overrides.
"""
pass
def remove_from_studio_default(self, on_change_trigger=None):
"""Remove studio overrides from entity and it's children.
Reset values to openpype's default and mark entity to not store values
as studio overrides if entity is not under group.
This is wrapper method that handles on_change callbacks only when all
`_remove_from_studio_default` on all children happened. That is
important as value changes may trigger change callbacks that must be
ignored. Callbacks are triggered by entity where method was called.
Args:
on_change_trigger (list): Callbacks of `on_change` should be stored
to trigger them afterwards.
"""
initialized = False
if on_change_trigger is None:
if not self.can_remove_from_studio_default:
return
initialized = True
on_change_trigger = []
self._remove_from_studio_default(on_change_trigger)
if initialized:
for callback in on_change_trigger:
callback()
@abstractmethod
def _remove_from_studio_default(self, on_change_trigger):
"""Item's implementation to remove studio overrides.
Mark item as it does not have studio overrides unset studio
override values.
"""
pass
def add_to_project_override(self, on_change_trigger=None):
initialized = False
if on_change_trigger is None:
if not self.can_add_to_project_override:
return
initialized = True
on_change_trigger = []
self._add_to_project_override(on_change_trigger)
if initialized:
for callback in on_change_trigger:
callback()
@abstractmethod
def _add_to_project_override(self, on_change_trigger):
"""Item's implementation to set values as overriden for project.
Mark item and all it's children to be stored as project overrides.
"""
pass
def remove_from_project_override(self, on_change_trigger=None):
"""Remove project overrides from entity and it's children.
Reset values to studio overrides or openpype's default and mark entity
to not store values as project overrides if entity is not under group.
This is wrapper method that handles on_change callbacks only when all
`_remove_from_project_override` on all children happened. That is
important as value changes may trigger change callbacks that must be
ignored. Callbacks are triggered by entity where method was called.
Args:
on_change_trigger (list): Callbacks of `on_change` should be stored
to trigger them afterwards.
"""
if self._override_state is not OverrideState.PROJECT:
return
initialized = False
if on_change_trigger is None:
if not self.can_remove_from_project_override:
return
initialized = True
on_change_trigger = []
self._remove_from_project_override(on_change_trigger)
if initialized:
for callback in on_change_trigger:
callback()
@abstractmethod
def _remove_from_project_override(self, on_change_trigger):
"""Item's implementation to remove project overrides.
Mark item as does not have project overrides. Must not change
`was_overriden` attribute value.
Args:
on_change_trigger (list): Callbacks of `on_change` should be stored
to trigger them afterwards.
"""
pass
def reset_callbacks(self):
"""Clear any registered callbacks on entity and all children."""
self.on_change_callbacks = []
class ItemEntity(BaseItemEntity):
"""Item that is used as hierarchical entity.
Entity must have defined parent and can't be created outside it's parent.
Dynamically created entity is entity that can be removed from settings
hierarchy and it's key or existence is not defined in schemas. Are
created by `ListEntity` or `DictMutableKeysEntity`. Their information about
default value or modification is not relevant.
Args:
schema_data (dict): Schema data that defines entity behavior.
parent (BaseItemEntity): Parent entity that created this entity.
is_dynamic_item (bool): Entity should behave like dynamically created
entity.
"""
_default_label_wrap = {
"use_label_wrap": False,
"collapsible": True,
"collapsed": False
}
def __init__(self, schema_data, parent, is_dynamic_item=False):
super(ItemEntity, self).__init__(schema_data)
self.parent = parent
self.is_dynamic_item = is_dynamic_item
self.is_file = self.schema_data.get("is_file", False)
self.is_group = self.schema_data.get("is_group", False)
self.is_in_dynamic_item = bool(
not self.is_dynamic_item
and (self.parent.is_dynamic_item or self.parent.is_in_dynamic_item)
)
# Dynamic item can't have key defined in it-self
# - key is defined by it's parent
if self.is_dynamic_item:
self.require_key = False
# If value should be stored to environments and uder which group key
# - the key may be dynamically changed by it's parent on save
self.env_group_key = self.schema_data.get("env_group_key")
self.is_env_group = bool(self.env_group_key is not None)
# Root item reference
self.root_item = self.parent.root_item
# File item reference
if self.parent.is_file:
self.file_item = self.parent
elif self.parent.file_item:
self.file_item = self.parent.file_item
# Group item reference
if self.parent.is_group:
self.group_item = self.parent
elif self.parent.group_item:
self.group_item = self.parent.group_item
self.key = self.schema_data.get("key")
self.label = self.schema_data.get("label")
# GUI attributes
_default_label_wrap = self.__class__._default_label_wrap
for key, value in ItemEntity._default_label_wrap.items():
if key not in _default_label_wrap:
self.log.warning(
"Class {} miss default label wrap key \"{}\"".format(
self.__class__.__name__, key
)
)
_default_label_wrap[key] = value
use_label_wrap = self.schema_data.get("use_label_wrap")
if use_label_wrap is None:
if not self.label:
use_label_wrap = False
else:
use_label_wrap = _default_label_wrap["use_label_wrap"]
self.use_label_wrap = use_label_wrap
# Used only if `use_label_wrap` is set to True
self.collapsible = self.schema_data.get(
"collapsible",
_default_label_wrap["collapsible"]
)
self.collapsed = self.schema_data.get(
"collapsed",
_default_label_wrap["collapsed"]
)
self._item_initalization()
def save(self):
"""Call save on root item."""
self.root_item.save()
def schema_validations(self):
if not self.label and self.use_label_wrap:
reason = (
"Entity has set `use_label_wrap` to true but"
" does not have set `label`."
)
raise EntitySchemaError(self, reason)
super(ItemEntity, self).schema_validations()
def create_schema_object(self, *args, **kwargs):
"""Reference method for creation of entities defined in RootEntity."""
return self.root_item.create_schema_object(*args, **kwargs)
def get_entity_from_path(self, path):
return self.root_item.get_entity_from_path(path)
@abstractmethod
def update_default_value(self, parent_values):
"""Fill default values on startup or on refresh.
Default values stored in `openpype` repository should update all items
in schema. Each item should take values for his key and set it's value
or pass values down to children items.
Args:
parent_values (dict): Values of parent's item. But in case item is
used as widget, `parent_values` contain value for item.
"""
pass
@abstractmethod
def update_studio_value(self, parent_values):
"""Fill studio override values on startup or on refresh.
Set studio value if is not set to NOT_SET, in that case studio
overrides are not set yet.
Args:
parent_values (dict): Values of parent's item. But in case item is
used as widget, `parent_values` contain value for item.
"""
pass
@abstractmethod
def update_project_value(self, parent_values):
"""Fill project override values on startup, refresh or project change.
Set project value if is not set to NOT_SET, in that case project
overrides are not set yet.
Args:
parent_values (dict): Values of parent's item. But in case item is
used as widget, `parent_values` contain value for item.
"""
pass

View file

@ -0,0 +1,489 @@
import copy
from .lib import (
WRAPPER_TYPES,
OverrideState,
NOT_SET
)
from openpype.settings.constants import (
METADATA_KEYS,
M_OVERRIDEN_KEY,
KEY_REGEX
)
from . import (
BaseItemEntity,
ItemEntity,
BoolEntity,
GUIEntity
)
from .exceptions import (
SchemaDuplicatedKeys,
EntitySchemaError,
InvalidKeySymbols
)
class DictImmutableKeysEntity(ItemEntity):
"""Entity that represents dictionary with predefined keys.
Entity's keys can't be removed or added and children type is defined in
schema data.
It is possible to use entity similar way as `dict` object. Returned values
are not real settings values but entities representing the value.
"""
schema_types = ["dict"]
_default_label_wrap = {
"use_label_wrap": True,
"collapsible": True,
"collapsed": True
}
def __getitem__(self, key):
"""Return entity inder key."""
return self.non_gui_children[key]
def __setitem__(self, key, value):
"""Set value of item under key."""
child_obj = self.non_gui_children[key]
child_obj.set(value)
def __iter__(self):
"""Iter through keys."""
for key in self.keys():
yield key
def __contains__(self, key):
"""Check if key is available."""
return key in self.non_gui_children
def get(self, key, default=None):
"""Safe entity getter by key."""
return self.non_gui_children.get(key, default)
def keys(self):
"""Entity's keys."""
return self.non_gui_children.keys()
def values(self):
"""Children entities."""
return self.non_gui_children.values()
def items(self):
"""Children entities paired with their key (key, value)."""
return self.non_gui_children.items()
def set(self, value):
"""Set value."""
new_value = self.convert_to_valid_type(value)
for _key, _value in new_value.items():
self.non_gui_children[_key].set(_value)
def schema_validations(self):
"""Validation of schema data."""
children_keys = set()
for child_entity in self.children:
if not isinstance(child_entity, BaseItemEntity):
continue
elif child_entity.key not in children_keys:
children_keys.add(child_entity.key)
else:
raise SchemaDuplicatedKeys(self, child_entity.key)
for key in self.keys():
if not KEY_REGEX.match(key):
raise InvalidKeySymbols(self.path, key)
if self.checkbox_key:
checkbox_child = self.non_gui_children.get(self.checkbox_key)
if not checkbox_child:
reason = "Checkbox children \"{}\" was not found.".format(
self.checkbox_key
)
raise EntitySchemaError(self, reason)
if not isinstance(checkbox_child, BoolEntity):
reason = (
"Checkbox children \"{}\" is not `boolean` type."
).format(self.checkbox_key)
raise EntitySchemaError(self, reason)
super(DictImmutableKeysEntity, self).schema_validations()
# Trigger schema validation on children entities
for child_obj in self.children:
child_obj.schema_validations()
def on_change(self):
"""Update metadata on change and pass change to parent."""
self._update_current_metadata()
for callback in self.on_change_callbacks:
callback()
self.parent.on_child_change(self)
def on_child_change(self, _child_obj):
"""Trigger on change callback if child changes are not ignored."""
if not self._ignore_child_changes:
self.on_change()
def _add_children(self, schema_data, first=True):
"""Add children from schema data and separate gui wrappers.
Wrappers are stored in way so tool can create them and keep relation
to entities.
Args:
schema_data (dict): Schema data of an entity.
first (bool): Helper to know if was method called from inside of
method when handling gui wrappers.
"""
added_children = []
for children_schema in schema_data["children"]:
if children_schema["type"] in WRAPPER_TYPES:
_children_schema = copy.deepcopy(children_schema)
wrapper_children = self._add_children(
children_schema, False
)
_children_schema["children"] = wrapper_children
added_children.append(_children_schema)
continue
child_obj = self.create_schema_object(children_schema, self)
self.children.append(child_obj)
added_children.append(child_obj)
if isinstance(child_obj, GUIEntity):
continue
self.non_gui_children[child_obj.key] = child_obj
if not first:
return added_children
for child_obj in added_children:
self.gui_layout.append(child_obj)
def _item_initalization(self):
self._default_metadata = NOT_SET
self._studio_override_metadata = NOT_SET
self._project_override_metadata = NOT_SET
self._ignore_child_changes = False
# `current_metadata` are still when schema is loaded
# - only metadata stored with dict item are gorup overrides in
# M_OVERRIDEN_KEY
self._current_metadata = {}
self._metadata_are_modified = False
# Children are stored by key as keys are immutable and are defined by
# schema
self.valid_value_types = (dict, )
self.children = []
self.non_gui_children = {}
self.gui_layout = []
self._add_children(self.schema_data)
if self.is_dynamic_item:
self.require_key = False
# GUI attributes
self.checkbox_key = self.schema_data.get("checkbox_key")
self.highlight_content = self.schema_data.get(
"highlight_content", False
)
self.show_borders = self.schema_data.get("show_borders", True)
def get_child_path(self, child_obj):
"""Get hierarchical path of child entity.
Child must be entity's direct children.
"""
result_key = None
for key, _child_obj in self.non_gui_children.items():
if _child_obj is child_obj:
result_key = key
break
if result_key is None:
raise ValueError("Didn't found child {}".format(child_obj))
return "/".join([self.path, result_key])
def _update_current_metadata(self):
current_metadata = {}
for key, child_obj in self.non_gui_children.items():
if self._override_state is OverrideState.DEFAULTS:
break
if not child_obj.is_group:
continue
if (
self._override_state is OverrideState.STUDIO
and not child_obj.has_studio_override
):
continue
if (
self._override_state is OverrideState.PROJECT
and not child_obj.has_project_override
):
continue
if M_OVERRIDEN_KEY not in current_metadata:
current_metadata[M_OVERRIDEN_KEY] = []
current_metadata[M_OVERRIDEN_KEY].append(key)
# Define if current metadata are avaialble for current override state
metadata = NOT_SET
if self._override_state is OverrideState.STUDIO:
metadata = self._studio_override_metadata
elif self._override_state is OverrideState.PROJECT:
metadata = self._project_override_metadata
if metadata is NOT_SET:
metadata = {}
self._metadata_are_modified = current_metadata != metadata
self._current_metadata = current_metadata
def set_override_state(self, state):
# Trigger override state change of root if is not same
if self.root_item.override_state is not state:
self.root_item.set_override_state(state)
return
# Change has/had override states
self._override_state = state
for child_obj in self.non_gui_children.values():
child_obj.set_override_state(state)
self._update_current_metadata()
@property
def value(self):
output = {}
for key, child_obj in self.non_gui_children.items():
output[key] = child_obj.value
return output
@property
def has_unsaved_changes(self):
if self._metadata_are_modified:
return True
return self._child_has_unsaved_changes
@property
def _child_has_unsaved_changes(self):
for child_obj in self.non_gui_children.values():
if child_obj.has_unsaved_changes:
return True
return False
@property
def has_studio_override(self):
return self._child_has_studio_override
@property
def _child_has_studio_override(self):
if self._override_state >= OverrideState.STUDIO:
for child_obj in self.non_gui_children.values():
if child_obj.has_studio_override:
return True
return False
@property
def has_project_override(self):
return self._child_has_project_override
@property
def _child_has_project_override(self):
if self._override_state >= OverrideState.PROJECT:
for child_obj in self.non_gui_children.values():
if child_obj.has_project_override:
return True
return False
def settings_value(self):
if self._override_state is OverrideState.NOT_DEFINED:
return NOT_SET
if self._override_state is OverrideState.DEFAULTS:
output = {}
for key, child_obj in self.non_gui_children.items():
child_value = child_obj.settings_value()
if not child_obj.is_file and not child_obj.file_item:
for _key, _value in child_value.items():
new_key = "/".join([key, _key])
output[new_key] = _value
else:
output[key] = child_value
return output
if self.is_group:
if self._override_state is OverrideState.STUDIO:
if not self.has_studio_override:
return NOT_SET
elif self._override_state is OverrideState.PROJECT:
if not self.has_project_override:
return NOT_SET
output = {}
for key, child_obj in self.non_gui_children.items():
value = child_obj.settings_value()
if value is not NOT_SET:
output[key] = value
if not output:
return NOT_SET
output.update(self._current_metadata)
return output
def _prepare_value(self, value):
if value is NOT_SET:
return NOT_SET, NOT_SET
# Create copy of value before poping values
value = copy.deepcopy(value)
metadata = {}
for key in METADATA_KEYS:
if key in value:
metadata[key] = value.pop(key)
return value, metadata
def update_default_value(self, value):
"""Update default values.
Not an api method, should be called by parent.
"""
value = self._check_update_value(value, "default")
self.has_default_value = value is not NOT_SET
# TODO add value validation
value, metadata = self._prepare_value(value)
self._default_metadata = metadata
if value is NOT_SET:
for child_obj in self.non_gui_children.values():
child_obj.update_default_value(value)
return
value_keys = set(value.keys())
expected_keys = set(self.non_gui_children)
unknown_keys = value_keys - expected_keys
if unknown_keys:
self.log.warning(
"{} Unknown keys in default values: {}".format(
self.path,
", ".join("\"{}\"".format(key) for key in unknown_keys)
)
)
for key, child_obj in self.non_gui_children.items():
child_value = value.get(key, NOT_SET)
child_obj.update_default_value(child_value)
def update_studio_value(self, value):
"""Update studio override values.
Not an api method, should be called by parent.
"""
value = self._check_update_value(value, "studio override")
value, metadata = self._prepare_value(value)
self._studio_override_metadata = metadata
self.had_studio_override = metadata is not NOT_SET
if value is NOT_SET:
for child_obj in self.non_gui_children.values():
child_obj.update_studio_value(value)
return
value_keys = set(value.keys())
expected_keys = set(self.non_gui_children)
unknown_keys = value_keys - expected_keys
if unknown_keys:
self.log.warning(
"{} Unknown keys in studio overrides: {}".format(
self.path,
", ".join("\"{}\"".format(key) for key in unknown_keys)
)
)
for key, child_obj in self.non_gui_children.items():
child_value = value.get(key, NOT_SET)
child_obj.update_studio_value(child_value)
def update_project_value(self, value):
"""Update project override values.
Not an api method, should be called by parent.
"""
value = self._check_update_value(value, "project override")
value, metadata = self._prepare_value(value)
self._project_override_metadata = metadata
self.had_project_override = metadata is not NOT_SET
if value is NOT_SET:
for child_obj in self.non_gui_children.values():
child_obj.update_project_value(value)
return
value_keys = set(value.keys())
expected_keys = set(self.non_gui_children)
unknown_keys = value_keys - expected_keys
if unknown_keys:
self.log.warning(
"{} Unknown keys in project overrides: {}".format(
self.path,
", ".join("\"{}\"".format(key) for key in unknown_keys)
)
)
for key, child_obj in self.non_gui_children.items():
child_value = value.get(key, NOT_SET)
child_obj.update_project_value(child_value)
def _discard_changes(self, on_change_trigger):
self._ignore_child_changes = True
for child_obj in self.non_gui_children.values():
child_obj.discard_changes(on_change_trigger)
self._ignore_child_changes = False
def _add_to_studio_default(self, on_change_trigger):
self._ignore_child_changes = True
for child_obj in self.non_gui_children.values():
child_obj.add_to_studio_default(on_change_trigger)
self._ignore_child_changes = False
self.parent.on_child_change(self)
def _remove_from_studio_default(self, on_change_trigger):
self._ignore_child_changes = True
for child_obj in self.non_gui_children.values():
child_obj.remove_from_studio_default(on_change_trigger)
self._ignore_child_changes = False
def _add_to_project_override(self, _on_change_trigger):
self._ignore_child_changes = True
for child_obj in self.non_gui_children.values():
child_obj.add_to_project_override(_on_change_trigger)
self._ignore_child_changes = False
self.parent.on_child_change(self)
def _remove_from_project_override(self, on_change_trigger):
if self._override_state is not OverrideState.PROJECT:
return
self._ignore_child_changes = True
for child_obj in self.non_gui_children.values():
child_obj.remove_from_project_override(on_change_trigger)
self._ignore_child_changes = False
def reset_callbacks(self):
"""Reset registered callbacks on entity and children."""
super(DictImmutableKeysEntity, self).reset_callbacks()
for child_entity in self.children:
child_entity.reset_callbacks()

View file

@ -0,0 +1,587 @@
import re
import copy
from .lib import (
NOT_SET,
OverrideState
)
from . import EndpointEntity
from .exceptions import (
DefaultsNotDefined,
InvalidKeySymbols,
StudioDefaultsNotDefined,
RequiredKeyModified,
EntitySchemaError
)
from openpype.settings.constants import (
METADATA_KEYS,
M_DYNAMIC_KEY_LABEL,
M_ENVIRONMENT_KEY,
KEY_REGEX,
KEY_ALLOWED_SYMBOLS
)
class DictMutableKeysEntity(EndpointEntity):
"""Dictionary entity that has mutable keys.
Keys of entity's children can be modified, removed or added. Children have
defined entity type so it is not possible to have 2 different entity types
as children.
TODOs:
- cleanup children on pop
- remove child's reference to parent
- clear callbacks
"""
schema_types = ["dict-modifiable"]
_default_label_wrap = {
"use_label_wrap": True,
"collapsible": True,
"collapsed": True
}
_miss_arg = object()
def __getitem__(self, key):
if key not in self.children_by_key:
self.add_key(key)
return self.children_by_key[key]
def __setitem__(self, key, value):
self.set_key_value(key, value)
def __iter__(self):
for key in self.keys():
yield key
def __contains__(self, key):
return key in self.children_by_key
def pop(self, key, *args, **kwargs):
if key in self.required_keys:
raise RequiredKeyModified(self.path, key)
result = self.children_by_key.pop(key, *args, **kwargs)
self.on_change()
return result
def get(self, key, default=None):
return self.children_by_key.get(key, default)
def keys(self):
return self.children_by_key.keys()
def values(self):
return self.children_by_key.values()
def items(self):
return self.children_by_key.items()
def clear(self):
for key in tuple(self.children_by_key.keys()):
self.pop(key)
def set(self, value):
new_value = self.convert_to_valid_type(value)
prev_keys = set(self.keys())
for _key, _value in new_value.items():
self.set_key_value(_key, _value)
if _key in prev_keys:
prev_keys.remove(_key)
for key in prev_keys:
self.pop(key)
def set_key_value(self, key, value):
# TODO Check for value type if is Settings entity?
child_obj = self.children_by_key.get(key)
if not child_obj:
if not KEY_REGEX.match(key):
raise InvalidKeySymbols(self.path, key)
child_obj = self.add_key(key)
child_obj.set(value)
def change_key(self, old_key, new_key):
if old_key in self.required_keys:
raise RequiredKeyModified(self.path, old_key)
if new_key == old_key:
return
if not KEY_REGEX.match(new_key):
raise InvalidKeySymbols(self.path, new_key)
self.children_by_key[new_key] = self.children_by_key.pop(old_key)
self._on_key_label_change()
def _on_key_label_change(self):
if self._override_state is OverrideState.STUDIO:
self._has_studio_override = True
elif self._override_state is OverrideState.PROJECT:
self._has_project_override = True
self.on_change()
def _add_key(self, key):
if key in self.children_by_key:
self.pop(key)
if not KEY_REGEX.match(key):
raise InvalidKeySymbols(self.path, key)
if self.value_is_env_group:
item_schema = copy.deepcopy(self.item_schema)
item_schema["env_group_key"] = key
else:
item_schema = self.item_schema
new_child = self.create_schema_object(item_schema, self, True)
self.children_by_key[key] = new_child
return new_child
def add_key(self, key):
new_child = self._add_key(key)
new_child.set_override_state(self._override_state)
self.on_change()
return new_child
def change_child_key(self, child_entity, new_key):
old_key = None
for key, child in self.children_by_key.items():
if child is child_entity:
old_key = key
break
self.change_key(old_key, new_key)
def get_child_key(self, child_entity):
for key, child in self.children_by_key.items():
if child is child_entity:
return key
return None
# Label methods
def get_child_label(self, child_entity):
return self.children_label_by_id.get(child_entity.id)
def set_child_label(self, child_entity, label):
self.children_label_by_id[child_entity.id] = label
self._on_key_label_change()
def get_key_label(self, key):
child_entity = self.children_by_key[key]
return self.get_child_label(child_entity)
def set_key_label(self, key, label):
child_entity = self.children_by_key[key]
self.set_child_label(child_entity, label)
def _item_initalization(self):
self._default_metadata = {}
self._studio_override_metadata = {}
self._project_override_metadata = {}
self.initial_value = None
self._ignore_child_changes = False
self.valid_value_types = (dict, )
self.value_on_not_set = {}
self.children_by_key = {}
self.children_label_by_id = {}
self.value_is_env_group = (
self.schema_data.get("value_is_env_group") or False
)
self.required_keys = self.schema_data.get("required_keys") or []
self.collapsible_key = self.schema_data.get("collapsible_key") or False
# GUI attributes
self.hightlight_content = (
self.schema_data.get("highlight_content") or False
)
object_type = self.schema_data.get("object_type") or {}
if not isinstance(object_type, dict):
# Backwards compatibility
object_type = {
"type": object_type
}
input_modifiers = self.schema_data.get("input_modifiers") or {}
if input_modifiers:
self.log.warning((
"Used deprecated key `input_modifiers` to define item."
" Rather use `object_type` as dictionary with modifiers."
))
object_type.update(input_modifiers)
self.item_schema = object_type
if self.value_is_env_group:
self.item_schema["env_group_key"] = ""
if not self.group_item:
self.is_group = True
def schema_validations(self):
super(DictMutableKeysEntity, self).schema_validations()
if not self.schema_data.get("object_type"):
reason = (
"Modifiable dictionary must have specified `object_type`."
)
raise EntitySchemaError(self, reason)
# TODO Ability to store labels should be defined with different key
if self.collapsible_key and not self.file_item:
reason = (
"Modifiable dictionary with collapsible keys is not under"
" file item so can't store metadata."
)
raise EntitySchemaError(self, reason)
for child_obj in self.children_by_key.values():
child_obj.schema_validations()
def get_child_path(self, child_obj):
result_key = None
for key, _child_obj in self.children_by_key.items():
if _child_obj is child_obj:
result_key = key
break
if result_key is None:
raise ValueError("Didn't found child {}".format(child_obj))
return "/".join([self.path, result_key])
def on_child_change(self, _child_entity):
if self._ignore_child_changes:
return
if self._override_state is OverrideState.STUDIO:
self._has_studio_override = True
elif self._override_state is OverrideState.PROJECT:
self._has_project_override = True
self.on_change()
def _metadata_for_current_state(self):
if (
self._override_state is OverrideState.PROJECT
and self._project_override_value is not NOT_SET
):
return self._project_override_metadata
if (
self._override_state >= OverrideState.STUDIO
and self._studio_override_value is not NOT_SET
):
return self._studio_override_metadata
return self._default_metadata
def set_override_state(self, state):
# Trigger override state change of root if is not same
if self.root_item.override_state is not state:
self.root_item.set_override_state(state)
return
# TODO change metadata
self._override_state = state
# Ignore if is dynamic item and use default in that case
if not self.is_dynamic_item and not self.is_in_dynamic_item:
if state > OverrideState.DEFAULTS:
if not self.has_default_value:
raise DefaultsNotDefined(self)
elif state > OverrideState.STUDIO:
if not self.had_studio_override:
raise StudioDefaultsNotDefined(self)
if state is OverrideState.STUDIO:
self._has_studio_override = self.had_studio_override
elif state is OverrideState.PROJECT:
self._has_project_override = self.had_project_override
self._has_studio_override = self.had_studio_override
using_project_overrides = False
using_studio_overrides = False
if (
state is OverrideState.PROJECT
and self.had_project_override
):
using_project_overrides = True
value = self._project_override_value
metadata = self._project_override_metadata
elif (
state >= OverrideState.STUDIO
and self.had_studio_override
):
using_studio_overrides = True
value = self._studio_override_value
metadata = self._studio_override_metadata
else:
value = self._default_value
metadata = self._default_metadata
if value is NOT_SET:
value = self.value_on_not_set
new_value = copy.deepcopy(value)
# Simulate `clear` method without triggering value change
for key in tuple(self.children_by_key.keys()):
self.children_by_key.pop(key)
for required_key in self.required_keys:
if required_key not in new_value:
new_value[required_key] = NOT_SET
# Create new children
children_label_by_id = {}
metadata_labels = metadata.get(M_DYNAMIC_KEY_LABEL) or {}
for _key, _value in new_value.items():
if not KEY_REGEX.match(_key):
# Replace invalid characters with underscore
# - this is safety to not break already existing settings
_key = re.sub(
r"[^{}]+".format(KEY_ALLOWED_SYMBOLS),
"_",
_key
)
child_entity = self._add_key(_key)
child_entity.update_default_value(_value)
if using_project_overrides:
child_entity.update_project_value(_value)
elif using_studio_overrides:
child_entity.update_studio_value(_value)
label = metadata_labels.get(_key)
if label:
children_label_by_id[child_entity.id] = label
child_entity.set_override_state(state)
self.children_label_by_id = children_label_by_id
self.initial_value = self.settings_value()
def children_key_by_id(self):
return {
child_entity.id: key
for key, child_entity in self.children_by_key.items()
}
@property
def value(self):
output = {}
for key, child_entity in self.children_by_key.items():
output[key] = child_entity.value
return output
@property
def metadata(self):
output = {}
if not self.children_label_by_id:
return output
children_key_by_id = self.children_key_by_id()
label_metadata = {}
for child_id, label in self.children_label_by_id.items():
key = children_key_by_id.get(child_id)
if key:
label_metadata[key] = label
output[M_DYNAMIC_KEY_LABEL] = label_metadata
return output
@property
def has_unsaved_changes(self):
if (
self._override_state is OverrideState.PROJECT
and self._has_project_override != self.had_project_override
):
return True
elif (
self._override_state is OverrideState.STUDIO
and self._has_studio_override != self.had_studio_override
):
return True
if self._child_has_unsaved_changes:
return True
if self.metadata != self._metadata_for_current_state():
return True
if self.settings_value() != self.initial_value:
return True
return False
@property
def _child_has_unsaved_changes(self):
for child_obj in self.children_by_key.values():
if child_obj.has_unsaved_changes:
return True
return False
@property
def has_studio_override(self):
return self._has_studio_override or self._child_has_studio_override
@property
def _child_has_studio_override(self):
if self._override_state >= OverrideState.STUDIO:
for child_obj in self.children_by_key.values():
if child_obj.has_studio_override:
return True
return False
@property
def has_project_override(self):
return self._has_project_override or self._child_has_project_override
@property
def _child_has_project_override(self):
if self._override_state >= OverrideState.PROJECT:
for child_obj in self.children_by_key.values():
if child_obj.has_project_override:
return True
return False
def _settings_value(self):
output = {}
for key, child_entity in self.children_by_key.items():
child_value = child_entity.settings_value()
# TODO child should have setter of env group key se child can
# know what env group represents.
if self.value_is_env_group:
if key not in child_value[M_ENVIRONMENT_KEY]:
_metadata = child_value[M_ENVIRONMENT_KEY]
_m_keykey = tuple(_metadata.keys())[0]
env_keys = child_value[M_ENVIRONMENT_KEY].pop(_m_keykey)
child_value[M_ENVIRONMENT_KEY][key] = env_keys
output[key] = child_value
output.update(self.metadata)
return output
def _prepare_value(self, value):
metadata = {}
if isinstance(value, dict):
for key in METADATA_KEYS:
if key in value:
metadata[key] = value.pop(key)
return value, metadata
def update_default_value(self, value):
value = self._check_update_value(value, "default")
has_default_value = value is not NOT_SET
if has_default_value:
for required_key in self.required_keys:
if required_key not in value:
has_default_value = False
break
self.has_default_value = has_default_value
value, metadata = self._prepare_value(value)
self._default_value = value
self._default_metadata = metadata
def update_studio_value(self, value):
value = self._check_update_value(value, "studio override")
value, metadata = self._prepare_value(value)
self._studio_override_value = value
self._studio_override_metadata = metadata
self.had_studio_override = value is not NOT_SET
def update_project_value(self, value):
value = self._check_update_value(value, "project override")
value, metadata = self._prepare_value(value)
self._project_override_value = value
self._project_override_metadata = metadata
self.had_project_override = value is not NOT_SET
def _discard_changes(self, on_change_trigger):
self.set_override_state(self._override_state)
on_change_trigger.append(self.on_change)
def _add_to_studio_default(self, _on_change_trigger):
self._has_studio_override = True
self.on_change()
def _remove_from_studio_default(self, on_change_trigger):
value = self._default_value
if value is NOT_SET:
value = self.value_on_not_set
new_value = copy.deepcopy(value)
self._ignore_child_changes = True
# Simulate `clear` method without triggering value change
for key in tuple(self.children_by_key.keys()):
child_obj = self.children_by_key.pop(key)
# Create new children
for _key, _value in new_value.items():
child_obj = self._add_key(_key)
child_obj.update_default_value(_value)
child_obj.set_override_state(self._override_state)
self._ignore_child_changes = False
self._has_studio_override = False
on_change_trigger.append(self.on_change)
def _add_to_project_override(self, _on_change_trigger):
self._has_project_override = True
self.on_change()
def _remove_from_project_override(self, on_change_trigger):
if self._override_state is not OverrideState.PROJECT:
return
if not self.has_project_override:
return
if self._has_studio_override:
value = self._studio_override_value
elif self.has_default_value:
value = self._default_value
else:
value = self.value_on_not_set
new_value = copy.deepcopy(value)
self._ignore_child_changes = True
# Simulate `clear` method without triggering value change
for key in tuple(self.children_by_key.keys()):
child_obj = self.children_by_key.pop(key)
# Create new children
for _key, _value in new_value.items():
child_obj = self._add_key(_key)
child_obj.update_default_value(_value)
if self._has_studio_override:
child_obj.update_studio_value(_value)
child_obj.set_override_state(self._override_state)
self._ignore_child_changes = False
self._has_project_override = False
on_change_trigger.append(self.on_change)
def reset_callbacks(self):
super(DictMutableKeysEntity, self).reset_callbacks()
for child_entity in self.children_by_key.values():
child_entity.reset_callbacks()

View file

@ -0,0 +1,211 @@
from .input_entities import InputEntity
from .exceptions import EntitySchemaError
from .lib import (
NOT_SET,
STRING_TYPE
)
class BaseEnumEntity(InputEntity):
def _item_initalization(self):
self.multiselection = True
self.value_on_not_set = None
self.enum_items = None
self.valid_keys = None
self.valid_value_types = None
self.placeholder = None
def schema_validations(self):
if not isinstance(self.enum_items, list):
raise EntitySchemaError(
self, "Enum item must have defined `enum_items` as list."
)
enum_keys = set()
for item in self.enum_items:
key = tuple(item.keys())[0]
if key in enum_keys:
reason = "Key \"{}\" is more than once in enum items.".format(
key
)
raise EntitySchemaError(self, reason)
enum_keys.add(key)
if not isinstance(key, STRING_TYPE):
reason = "Key \"{}\" has invalid type {}, expected {}.".format(
key, type(key), STRING_TYPE
)
raise EntitySchemaError(self, reason)
super(BaseEnumEntity, self).schema_validations()
def _convert_to_valid_type(self, value):
if self.multiselection:
if isinstance(value, (set, tuple)):
return list(value)
elif isinstance(value, (int, float)):
return str(value)
return NOT_SET
def set(self, value):
new_value = self.convert_to_valid_type(value)
if self.multiselection:
check_values = new_value
else:
check_values = [new_value]
for item in check_values:
if item not in self.valid_keys:
raise ValueError(
"{} Invalid value \"{}\". Expected one of: {}".format(
self.path, item, self.valid_keys
)
)
self._current_value = new_value
self._on_value_change()
class EnumEntity(BaseEnumEntity):
schema_types = ["enum"]
def _item_initalization(self):
self.multiselection = self.schema_data.get("multiselection", False)
self.enum_items = self.schema_data.get("enum_items")
valid_keys = set()
for item in self.enum_items or []:
valid_keys.add(tuple(item.keys())[0])
self.valid_keys = valid_keys
if self.multiselection:
self.valid_value_types = (list, )
self.value_on_not_set = []
else:
for key in valid_keys:
if self.value_on_not_set is NOT_SET:
self.value_on_not_set = key
break
self.valid_value_types = (STRING_TYPE, )
# GUI attribute
self.placeholder = self.schema_data.get("placeholder")
def schema_validations(self):
if not self.enum_items and "enum_items" not in self.schema_data:
raise EntitySchemaError(
self, "Enum item must have defined `enum_items`"
)
super(EnumEntity, self).schema_validations()
class AppsEnumEntity(BaseEnumEntity):
schema_types = ["apps-enum"]
def _item_initalization(self):
self.multiselection = True
self.value_on_not_set = []
self.enum_items = []
self.valid_keys = set()
self.valid_value_types = (list, )
self.placeholder = None
def _get_enum_values(self):
system_settings_entity = self.get_entity_from_path("system_settings")
valid_keys = set()
enum_items = []
applications_entity = system_settings_entity["applications"]
for group_name, app_group in applications_entity.items():
enabled_entity = app_group.get("enabled")
if enabled_entity and not enabled_entity.value:
continue
host_name_entity = app_group.get("host_name")
if not host_name_entity or not host_name_entity.value:
continue
group_label = app_group["label"].value
for variant_name, variant_entity in app_group["variants"].items():
enabled_entity = variant_entity.get("enabled")
if enabled_entity and not enabled_entity.value:
continue
variant_label = variant_entity["variant_label"].value
if group_label:
full_label = "{} {}".format(group_label, variant_label)
else:
full_label = variant_label
full_name = "/".join((group_name, variant_name))
enum_items.append({full_name: full_label})
valid_keys.add(full_name)
return enum_items, valid_keys
def set_override_state(self, *args, **kwargs):
super(AppsEnumEntity, self).set_override_state(*args, **kwargs)
self.enum_items, self.valid_keys = self._get_enum_values()
new_value = []
for key in self._current_value:
if key in self.valid_keys:
new_value.append(key)
self._current_value = new_value
class ToolsEnumEntity(BaseEnumEntity):
schema_types = ["tools-enum"]
def _item_initalization(self):
self.multiselection = True
self.value_on_not_set = []
self.enum_items = []
self.valid_keys = set()
self.valid_value_types = (list, )
self.placeholder = None
def _get_enum_values(self):
system_settings_entity = self.get_entity_from_path("system_settings")
valid_keys = set()
enum_items = []
tool_groups_entity = system_settings_entity["tools"]["tool_groups"]
for group_name, tool_group in tool_groups_entity.items():
# Try to get group label from entity
group_label = None
if hasattr(tool_groups_entity, "get_key_label"):
group_label = tool_groups_entity.get_key_label(group_name)
variants_entity = tool_group["variants"]
for variant_name in variants_entity.keys():
# Prepare tool name (used as value)
tool_name = "/".join((group_name, variant_name))
# Try to get variant label from entity
variant_label = None
if hasattr(variants_entity, "get_key_label"):
variant_label = variants_entity.get_key_label(variant_name)
# Tool label that will be shown
# - use tool name itself if labels are not filled
if group_label and variant_label:
tool_label = " ".join((group_label, variant_label))
else:
tool_label = tool_name
enum_items.append({tool_name: tool_label})
valid_keys.add(tool_name)
return enum_items, valid_keys
def set_override_state(self, *args, **kwargs):
super(ToolsEnumEntity, self).set_override_state(*args, **kwargs)
self.enum_items, self.valid_keys = self._get_enum_values()
new_value = []
for key in self._current_value:
if key in self.valid_keys:
new_value.append(key)
self._current_value = new_value

View file

@ -0,0 +1,125 @@
from openpype.settings.constants import KEY_ALLOWED_SYMBOLS
class DefaultsNotDefined(Exception):
def __init__(self, obj):
msg = "Default values for object are not set. {}".format(obj.path)
super(DefaultsNotDefined, self).__init__(msg)
class StudioDefaultsNotDefined(Exception):
def __init__(self, obj):
msg = "Studio default values for object are not set. {}".format(
obj.path
)
super(StudioDefaultsNotDefined, self).__init__(msg)
class InvalidValueType(Exception):
msg_template = "{}"
def __init__(self, valid_types, invalid_type, path):
msg = "Path \"{}\". ".format(path)
joined_types = ", ".join(
[str(valid_type) for valid_type in valid_types]
)
msg += "Got invalid type \"{}\". Expected: {}".format(
invalid_type, joined_types
)
self.msg = msg
super(InvalidValueType, self).__init__(msg)
class RequiredKeyModified(KeyError):
def __init__(self, entity_path, key):
msg = "{} - Tried to modify required key \"{}\"."
super(RequiredKeyModified, self).__init__(msg.format(entity_path, key))
class InvalidKeySymbols(KeyError):
def __init__(self, entity_path, key):
msg = "{} - Invalid key \"{}\". Allowed symbols are {}"
super(InvalidKeySymbols, self).__init__(
msg.format(entity_path, key, KEY_ALLOWED_SYMBOLS)
)
class SchemaError(Exception):
pass
class EntitySchemaError(SchemaError):
def __init__(self, entity, reason):
self.entity = entity
self.reason = reason
msg = "{} {} - {}".format(entity.__class__, entity.path, reason)
super(EntitySchemaError, self).__init__(msg)
class SchemeGroupHierarchyBug(EntitySchemaError):
def __init__(self, entity):
reason = (
"Items with attribute \"is_group\" can't have another item with"
" \"is_group\" attribute as child."
)
super(SchemeGroupHierarchyBug, self).__init__(entity, reason)
class SchemaMissingFileInfo(SchemaError):
def __init__(self, invalid):
full_path_keys = []
for item in invalid:
full_path_keys.append("\"{}\"".format("/".join(item)))
msg = (
"Schema has missing definition of output file (\"is_file\" key)"
" for keys. [{}]"
).format(", ".join(full_path_keys))
super(SchemaMissingFileInfo, self).__init__(msg)
class SchemaDuplicatedKeys(SchemaError):
def __init__(self, entity, key):
msg = (
"Schema item contain duplicated key \"{}\" in"
" one hierarchy level."
).format(key)
super(SchemaDuplicatedKeys, self).__init__(entity, msg)
class SchemaDuplicatedEnvGroupKeys(SchemaError):
def __init__(self, invalid):
items = []
for key_path, keys in invalid.items():
joined_keys = ", ".join([
"\"{}\"".format(key) for key in keys
])
items.append("\"{}\" ({})".format(key_path, joined_keys))
msg = (
"Schema items contain duplicated environment group keys. {}"
).format(" || ".join(items))
super(SchemaDuplicatedEnvGroupKeys, self).__init__(msg)
class SchemaTemplateMissingKeys(SchemaError):
def __init__(self, missing_keys, required_keys, template_name=None):
self.missing_keys = missing_keys
self.required_keys = required_keys
if template_name:
msg = "Schema template \"{}\" require more keys.\n".format(
template_name
)
else:
msg = ""
msg += "Required keys: {}\nMissing keys: {}".format(
self.join_keys(required_keys),
self.join_keys(missing_keys)
)
super(SchemaTemplateMissingKeys, self).__init__(msg)
def join_keys(self, keys):
return ", ".join([
"\"{}\"".format(key) for key in keys
])

View file

@ -0,0 +1,513 @@
import re
import copy
from abc import abstractmethod
from .base_entity import ItemEntity
from .lib import (
NOT_SET,
STRING_TYPE,
OverrideState
)
from .exceptions import (
DefaultsNotDefined,
StudioDefaultsNotDefined,
EntitySchemaError
)
from openpype.settings.constants import (
METADATA_KEYS,
M_ENVIRONMENT_KEY
)
class EndpointEntity(ItemEntity):
"""Entity that is a endpoint of settings value.
In most of cases endpoint entity does not have children entities and if has
then they are dynamic and can be removed/created. Is automatically set as
group if any parent is not, that is because of override metadata.
"""
def __init__(self, *args, **kwargs):
super(EndpointEntity, self).__init__(*args, **kwargs)
if (
not (self.group_item or self.is_group)
and not (self.is_dynamic_item or self.is_in_dynamic_item)
):
self.is_group = True
def schema_validations(self):
"""Validation of entity schema and schema hierarchy."""
# Default value when even defaults are not filled must be set
if self.value_on_not_set is NOT_SET:
reason = "Attribute `value_on_not_set` is not filled. {}".format(
self.__class__.__name__
)
raise EntitySchemaError(self, reason)
super(EndpointEntity, self).schema_validations()
@abstractmethod
def _settings_value(self):
pass
def settings_value(self):
if self._override_state is OverrideState.NOT_DEFINED:
return NOT_SET
if self.is_group:
if self._override_state is OverrideState.STUDIO:
if not self.has_studio_override:
return NOT_SET
elif self._override_state is OverrideState.PROJECT:
if not self.has_project_override:
return NOT_SET
return self._settings_value()
def on_change(self):
for callback in self.on_change_callbacks:
callback()
self.parent.on_child_change(self)
def update_default_value(self, value):
value = self._check_update_value(value, "default")
self._default_value = value
self.has_default_value = value is not NOT_SET
def update_studio_value(self, value):
value = self._check_update_value(value, "studio override")
self._studio_override_value = value
self.had_studio_override = bool(value is not NOT_SET)
def update_project_value(self, value):
value = self._check_update_value(value, "project override")
self._project_override_value = value
self.had_project_override = bool(value is not NOT_SET)
class InputEntity(EndpointEntity):
"""Endpoint entity without children."""
def __init__(self, *args, **kwargs):
super(InputEntity, self).__init__(*args, **kwargs)
self._value_is_modified = False
self._current_value = NOT_SET
def __eq__(self, other):
if isinstance(other, ItemEntity):
return self.value == other.value
return self.value == other
def get_child_path(self, child_obj):
raise TypeError("{} can't have children".format(
self.__class__.__name__
))
def schema_validations(self):
# Input entity must have file parent.
if not self.file_item:
raise EntitySchemaError(self, "Missing parent file entity.")
super(InputEntity, self).schema_validations()
@property
def value(self):
"""Entity's value without metadata."""
return self._current_value
def _settings_value(self):
return copy.deepcopy(self.value)
def set(self, value):
"""Change value."""
self._current_value = self.convert_to_valid_type(value)
self._on_value_change()
def _on_value_change(self):
# Change has_project_override attr value
if self._override_state is OverrideState.PROJECT:
self._has_project_override = True
elif self._override_state is OverrideState.STUDIO:
self._has_studio_override = True
self.on_change()
def on_change(self):
"""Callback triggered on change.
There are cases when this method may be called from other entity.
"""
value_is_modified = None
if self._override_state is OverrideState.PROJECT:
# Only value change
if (
self._has_project_override
and self._project_override_value is not NOT_SET
):
value_is_modified = (
self._current_value != self._project_override_value
)
if (
self._override_state is OverrideState.STUDIO
or value_is_modified is None
):
if (
self._has_studio_override
and self._studio_override_value is not NOT_SET
):
value_is_modified = (
self._current_value != self._studio_override_value
)
if value_is_modified is None:
value_is_modified = self._current_value != self._default_value
self._value_is_modified = value_is_modified
super(InputEntity, self).on_change()
def on_child_change(self, child_obj):
raise TypeError("Input entities do not contain children.")
@property
def has_unsaved_changes(self):
if self._override_state is OverrideState.NOT_DEFINED:
return False
if self._value_is_modified:
return True
# These may be stored on value change
if self._override_state is OverrideState.DEFAULTS:
if not self.has_default_value:
return True
elif self._override_state is OverrideState.STUDIO:
if self._has_studio_override != self.had_studio_override:
return True
if not self._has_studio_override and not self.has_default_value:
return True
elif self._override_state is OverrideState.PROJECT:
if self._has_project_override != self.had_project_override:
return True
if (
not self._has_project_override
and not self._has_studio_override
and not self.has_default_value
):
return True
return False
def set_override_state(self, state):
# Trigger override state change of root if is not same
if self.root_item.override_state is not state:
self.root_item.set_override_state(state)
return
self._override_state = state
# Ignore if is dynamic item and use default in that case
if not self.is_dynamic_item and not self.is_in_dynamic_item:
if state > OverrideState.DEFAULTS:
if not self.has_default_value:
raise DefaultsNotDefined(self)
elif state > OverrideState.STUDIO:
if not self.had_studio_override:
raise StudioDefaultsNotDefined(self)
if state is OverrideState.STUDIO:
self._has_studio_override = (
self._studio_override_value is not NOT_SET
)
elif state is OverrideState.PROJECT:
self._has_project_override = (
self._project_override_value is not NOT_SET
)
self._has_studio_override = self.had_studio_override
value = NOT_SET
if state is OverrideState.PROJECT:
value = self._project_override_value
if value is NOT_SET and state >= OverrideState.STUDIO:
value = self._studio_override_value
if value is NOT_SET and state >= OverrideState.DEFAULTS:
value = self._default_value
if value is NOT_SET:
value = self.value_on_not_set
self.has_default_value = False
else:
self.has_default_value = True
self._value_is_modified = False
self._current_value = copy.deepcopy(value)
def _discard_changes(self, on_change_trigger=None):
self._value_is_modified = False
if self._override_state >= OverrideState.PROJECT:
self._has_project_override = self.had_project_override
if self.had_project_override:
self._current_value = copy.deepcopy(
self._project_override_value
)
on_change_trigger.append(self.on_change)
return
if self._override_state >= OverrideState.STUDIO:
self._has_studio_override = self.had_studio_override
if self.had_studio_override:
self._current_value = copy.deepcopy(
self._studio_override_value
)
on_change_trigger.append(self.on_change)
return
if self._override_state >= OverrideState.DEFAULTS:
if self.has_default_value:
value = self._default_value
else:
value = self.value_on_not_set
self._current_value = copy.deepcopy(value)
on_change_trigger.append(self.on_change)
return
raise NotImplementedError("BUG: Unexcpected part of code.")
def _add_to_studio_default(self, _on_change_trigger):
self._has_studio_override = True
self.on_change()
def _remove_from_studio_default(self, on_change_trigger):
value = self._default_value
if value is NOT_SET:
value = self.value_on_not_set
self._current_value = copy.deepcopy(value)
self._has_studio_override = False
self._value_is_modified = False
on_change_trigger.append(self.on_change)
def _add_to_project_override(self, _on_change_trigger):
self._has_project_override = True
self.on_change()
def _remove_from_project_override(self, on_change_trigger):
if self._override_state is not OverrideState.PROJECT:
return
if not self._has_project_override:
return
self._has_project_override = False
if self._has_studio_override:
current_value = self._studio_override_value
elif self.has_default_value:
current_value = self._default_value
else:
current_value = self.value_on_not_set
self._current_value = copy.deepcopy(current_value)
on_change_trigger.append(self.on_change)
class NumberEntity(InputEntity):
schema_types = ["number"]
float_number_regex = re.compile(r"^\d+\.\d+$")
int_number_regex = re.compile(r"^\d+$")
def _item_initalization(self):
self.minimum = self.schema_data.get("minimum", -99999)
self.maximum = self.schema_data.get("maximum", 99999)
self.decimal = self.schema_data.get("decimal", 0)
value_on_not_set = self.schema_data.get("default", 0)
if self.decimal:
valid_value_types = (float, )
value_on_not_set = float(value_on_not_set)
else:
valid_value_types = (int, )
value_on_not_set = int(value_on_not_set)
self.valid_value_types = valid_value_types
self.value_on_not_set = value_on_not_set
def _convert_to_valid_type(self, value):
if isinstance(value, str):
new_value = None
if self.float_number_regex.match(value):
new_value = float(value)
elif self.int_number_regex.match(value):
new_value = int(value)
if new_value is not None:
self.log.info("{} - Converted str {} to {} {}".format(
self.path, value, type(new_value).__name__, new_value
))
value = new_value
if self.decimal:
if isinstance(value, float):
return value
if isinstance(value, int):
return float(value)
else:
if isinstance(value, int):
return value
if isinstance(value, float):
new_value = int(value)
if new_value != value:
self.log.info("{} - Converted float {} to int {}".format(
self.path, value, new_value
))
return new_value
return NOT_SET
class BoolEntity(InputEntity):
schema_types = ["boolean"]
def _item_initalization(self):
self.valid_value_types = (bool, )
self.value_on_not_set = True
class TextEntity(InputEntity):
schema_types = ["text"]
def _item_initalization(self):
self.valid_value_types = (STRING_TYPE, )
self.value_on_not_set = ""
# GUI attributes
self.multiline = self.schema_data.get("multiline", False)
self.placeholder_text = self.schema_data.get("placeholder")
def _convert_to_valid_type(self, value):
# Allow numbers converted to string
if isinstance(value, (int, float)):
return str(value)
return NOT_SET
class PathInput(InputEntity):
schema_types = ["path-input"]
def _item_initalization(self):
self.valid_value_types = (STRING_TYPE, )
self.value_on_not_set = ""
class RawJsonEntity(InputEntity):
schema_types = ["raw-json"]
def _item_initalization(self):
# Schema must define if valid value is dict or list
is_list = self.schema_data.get("is_list", False)
if is_list:
valid_value_types = (list, )
value_on_not_set = []
else:
valid_value_types = (dict, )
value_on_not_set = {}
self._is_list = is_list
self.valid_value_types = valid_value_types
self.value_on_not_set = value_on_not_set
self.default_metadata = {}
self.studio_override_metadata = {}
self.project_override_metadata = {}
@property
def is_list(self):
return self._is_list
@property
def is_dict(self):
return not self._is_list
def set(self, value):
new_value = self.convert_to_valid_type(value)
if isinstance(new_value, dict):
for key in METADATA_KEYS:
if key in new_value:
new_value.pop(key)
self._current_value = new_value
self._on_value_change()
@property
def metadata(self):
output = {}
if isinstance(self._current_value, dict) and self.is_env_group:
output[M_ENVIRONMENT_KEY] = {
self.env_group_key: list(self._current_value.keys())
}
return output
@property
def has_unsaved_changes(self):
result = super(RawJsonEntity, self).has_unsaved_changes
if not result:
result = self.metadata != self._metadata_for_current_state()
return result
def _metadata_for_current_state(self):
if (
self._override_state is OverrideState.PROJECT
and self._project_override_value is not NOT_SET
):
return self.project_override_metadata
if (
self._override_state >= OverrideState.STUDIO
and self._studio_override_value is not NOT_SET
):
return self.studio_override_metadata
return self.default_metadata
def _settings_value(self):
value = super(RawJsonEntity, self)._settings_value()
if self.is_env_group and isinstance(value, dict):
value.update(self.metadata)
return value
def _prepare_value(self, value):
metadata = {}
if isinstance(value, dict):
value = copy.deepcopy(value)
for key in METADATA_KEYS:
if key in value:
metadata[key] = value.pop(key)
return value, metadata
def update_default_value(self, value):
value = self._check_update_value(value, "default")
self.has_default_value = value is not NOT_SET
value, metadata = self._prepare_value(value)
self._default_value = value
self.default_metadata = metadata
def update_studio_value(self, value):
value = self._check_update_value(value, "studio override")
self.had_studio_override = value is not NOT_SET
value, metadata = self._prepare_value(value)
self._studio_override_value = value
self.studio_override_metadata = metadata
def update_project_value(self, value):
value = self._check_update_value(value, "project override")
self.had_project_override = value is not NOT_SET
value, metadata = self._prepare_value(value)
self._project_override_value = value
self.project_override_metadata = metadata

View file

@ -0,0 +1,456 @@
from .lib import (
NOT_SET,
STRING_TYPE,
OverrideState
)
from .exceptions import (
DefaultsNotDefined,
StudioDefaultsNotDefined,
EntitySchemaError
)
from .base_entity import ItemEntity
class PathEntity(ItemEntity):
schema_types = ["path"]
platforms = ("windows", "darwin", "linux")
platform_labels_mapping = {
"windows": "Windows",
"darwin": "MacOS",
"linux": "Linux"
}
path_item_type_error = "Got invalid path value type {}. Expected: {}"
attribute_error_msg = (
"'PathEntity' has no attribute '{}' if is not set as multiplatform"
)
def __setitem__(self, *args, **kwargs):
return self.child_obj.__setitem__(*args, **kwargs)
def __getitem__(self, *args, **kwargs):
return self.child_obj.__getitem__(*args, **kwargs)
def __iter__(self):
return self.child_obj.__iter__()
def keys(self):
if not self.multiplatform:
raise AttributeError(self.attribute_error_msg.format("keys"))
return self.child_obj.keys()
def values(self):
if not self.multiplatform:
raise AttributeError(self.attribute_error_msg.format("values"))
return self.child_obj.values()
def items(self):
if not self.multiplatform:
raise AttributeError(self.attribute_error_msg.format("items"))
return self.child_obj.items()
def _item_initalization(self):
if not self.group_item and not self.is_group:
self.is_group = True
self.multiplatform = self.schema_data.get("multiplatform", False)
self.multipath = self.schema_data.get("multipath", False)
# Create child object
if not self.multiplatform and not self.multipath:
valid_value_types = (STRING_TYPE, )
item_schema = {
"type": "path-input",
"key": self.key
}
elif not self.multiplatform:
valid_value_types = (list, )
item_schema = {
"type": "list",
"key": self.key,
"object_type": "path-input"
}
else:
valid_value_types = (dict, )
item_schema = {
"type": "dict",
"key": self.key,
"show_borders": False,
"children": []
}
for platform_key in self.platforms:
platform_label = self.platform_labels_mapping[platform_key]
child_item = {
"key": platform_key,
"label": platform_label
}
if self.multipath:
child_item["type"] = "list"
child_item["object_type"] = "path-input"
else:
child_item["type"] = "path-input"
item_schema["children"].append(child_item)
self.valid_value_types = valid_value_types
self.child_obj = self.create_schema_object(item_schema, self)
def get_child_path(self, _child_obj):
return self.path
def set(self, value):
self.child_obj.set(value)
def settings_value(self):
if self._override_state is OverrideState.NOT_DEFINED:
return NOT_SET
if self.is_group:
if self._override_state is OverrideState.STUDIO:
if not self.has_studio_override:
return NOT_SET
elif self._override_state is OverrideState.PROJECT:
if not self.has_project_override:
return NOT_SET
return self.child_obj.settings_value()
def on_change(self):
for callback in self.on_change_callbacks:
callback()
self.parent.on_child_change(self)
def on_child_change(self, _child_obj):
self.on_change()
@property
def has_unsaved_changes(self):
return self.child_obj.has_unsaved_changes
@property
def has_studio_override(self):
return self.child_obj.has_studio_override
@property
def has_project_override(self):
return self.child_obj.has_project_override
@property
def value(self):
return self.child_obj.value
def set_override_state(self, state):
# Trigger override state change of root if is not same
if self.root_item.override_state is not state:
self.root_item.set_override_state(state)
return
self._override_state = state
self.child_obj.set_override_state(state)
def update_default_value(self, value):
self.child_obj.update_default_value(value)
def update_project_value(self, value):
self.child_obj.update_project_value(value)
def update_studio_value(self, value):
self.child_obj.update_studio_value(value)
def _discard_changes(self, *args, **kwargs):
self.child_obj.discard_changes(*args, **kwargs)
def _add_to_studio_default(self, *args, **kwargs):
self.child_obj.add_to_studio_default(*args, **kwargs)
def _remove_from_studio_default(self, *args, **kwargs):
self.child_obj.remove_from_studio_default(*args, **kwargs)
def _add_to_project_override(self, *args, **kwargs):
self.child_obj.add_to_project_override(*args, **kwargs)
def _remove_from_project_override(self, *args, **kwargs):
self.child_obj.remove_from_project_override(*args, **kwargs)
def reset_callbacks(self):
super(PathEntity, self).reset_callbacks()
self.child_obj.reset_callbacks()
class ListStrictEntity(ItemEntity):
schema_types = ["list-strict"]
def _item_initalization(self):
self.valid_value_types = (list, )
self.require_key = True
self.initial_value = None
self._ignore_child_changes = False
# Child items
self.object_types = self.schema_data["object_types"]
self.children = []
for children_schema in self.object_types:
child_obj = self.create_schema_object(children_schema, self, True)
self.children.append(child_obj)
# GUI attribute
self.is_horizontal = self.schema_data.get("horizontal", True)
if not self.group_item and not self.is_group:
self.is_group = True
def schema_validations(self):
# List entity must have file parent.
if not self.file_item and not self.is_file:
raise EntitySchemaError(
self, "Missing file entity in hierarchy."
)
super(ListStrictEntity, self).schema_validations()
def get_child_path(self, child_obj):
result_idx = None
for idx, _child_obj in enumerate(self.children):
if _child_obj is child_obj:
result_idx = idx
break
if result_idx is None:
raise ValueError("Didn't found child {}".format(child_obj))
return "/".join([self.path, str(result_idx)])
@property
def value(self):
output = []
for child_obj in self.children:
output.append(child_obj.value)
return output
def set(self, value):
new_value = self.convert_to_valid_type(value)
for idx, item in enumerate(new_value):
self.children[idx].set(item)
def settings_value(self):
if self._override_state is OverrideState.NOT_DEFINED:
return NOT_SET
if (
self.is_group
and self._override_state is not OverrideState.DEFAULTS
):
if self._override_state is OverrideState.STUDIO:
if not self.has_studio_override:
return NOT_SET
elif self._override_state is OverrideState.PROJECT:
if not self.has_project_override:
return NOT_SET
output = []
for child_obj in self.children:
output.append(child_obj.settings_value())
return output
def on_change(self):
for callback in self.on_change_callbacks:
callback()
self.parent.on_child_change(self)
def on_child_change(self, _child_obj):
if self._ignore_child_changes:
return
if self._override_state is OverrideState.STUDIO:
self._has_studio_override = self._child_has_studio_override
elif self._override_state is OverrideState.PROJECT:
self._has_project_override = self._child_has_project_override
self.on_change()
@property
def has_unsaved_changes(self):
if self._override_state is OverrideState.NOT_DEFINED:
return False
if self._override_state is OverrideState.DEFAULTS:
if not self.has_default_value:
return True
elif self._override_state is OverrideState.STUDIO:
if self.had_studio_override != self._has_studio_override:
return True
if not self._has_studio_override and not self.has_default_value:
return True
elif self._override_state is OverrideState.PROJECT:
if self.had_project_override != self._has_project_override:
return True
if (
not self._has_project_override
and not self._has_studio_override
and not self.has_default_value
):
return True
if self._child_has_unsaved_changes:
return True
if self.settings_value() != self.initial_value:
return True
return False
@property
def has_studio_override(self):
return self._has_studio_override or self._child_has_studio_override
@property
def has_project_override(self):
return self._has_project_override or self._child_has_project_override
@property
def _child_has_unsaved_changes(self):
for child_obj in self.children:
if child_obj.has_unsaved_changes:
return True
return False
@property
def _child_has_studio_override(self):
for child_obj in self.children:
if child_obj.has_studio_override:
return True
return False
@property
def _child_has_project_override(self):
for child_obj in self.children:
if child_obj.has_project_override:
return True
return False
def set_override_state(self, state):
# Trigger override state change of root if is not same
if self.root_item.override_state is not state:
self.root_item.set_override_state(state)
return
self._override_state = state
# Ignore if is dynamic item and use default in that case
if not self.is_dynamic_item and not self.is_in_dynamic_item:
if state > OverrideState.DEFAULTS:
if not self.has_default_value:
raise DefaultsNotDefined(self)
elif state > OverrideState.STUDIO:
if not self.had_studio_override:
raise StudioDefaultsNotDefined(self)
for child_entity in self.children:
child_entity.set_override_state(state)
self.initial_value = self.settings_value()
def _discard_changes(self, on_change_trigger):
for child_obj in self.children:
child_obj.discard_changes(on_change_trigger)
def _add_to_studio_default(self, _on_change_trigger):
self._has_studio_override = True
self.on_change()
def _remove_from_studio_default(self, on_change_trigger):
self._ignore_child_changes = True
for child_obj in self.children:
child_obj.remove_from_studio_default(on_change_trigger)
self._ignore_child_changes = False
self._has_studio_override = False
def _add_to_project_override(self, _on_change_trigger):
self._has_project_override = True
self.on_change()
def _remove_from_project_override(self, on_change_trigger):
self._ignore_child_changes = True
for child_obj in self.children:
child_obj.remove_from_project_override(on_change_trigger)
self._ignore_child_changes = False
self._has_project_override = False
def _check_update_value(self, value, value_type):
value = super(ListStrictEntity, self)._check_update_value(
value, value_type
)
if value is NOT_SET:
return value
child_len = len(self.children)
value_len = len(value)
if value_len == child_len:
return value
self.log.warning(
(
"{} Amount of strict list items in {} values is"
" not same as expected. Expected {} items. Got {} items. {}"
).format(
self.path, value_type,
child_len, value_len, str(value)
)
)
if value_len < child_len:
# Fill missing values with NOT_SET
for _ in range(child_len - value_len):
value.append(NOT_SET)
else:
# Pop values that are overloaded
for _ in range(value_len - child_len):
value.pop(child_len)
return value
def update_default_value(self, value):
value = self._check_update_value(value, "default")
self.has_default_value = value is not NOT_SET
if value is NOT_SET:
for child_obj in self.children:
child_obj.update_default_value(value)
else:
for idx, item_value in enumerate(value):
self.children[idx].update_default_value(item_value)
def update_studio_value(self, value):
value = self._check_update_value(value, "studio override")
if value is NOT_SET:
for child_obj in self.children:
child_obj.update_studio_value(value)
else:
for idx, item_value in enumerate(value):
self.children[idx].update_studio_value(item_value)
def update_project_value(self, value):
value = self._check_update_value(value, "project override")
if value is NOT_SET:
for child_obj in self.children:
child_obj.update_project_value(value)
else:
for idx, item_value in enumerate(value):
self.children[idx].update_project_value(item_value)
def reset_callbacks(self):
super(ListStrictEntity, self).reset_callbacks()
self.child_obj.reset_callbacks()

View file

@ -0,0 +1,355 @@
import os
import re
import json
import copy
from .exceptions import (
SchemaTemplateMissingKeys,
SchemaDuplicatedEnvGroupKeys
)
try:
STRING_TYPE = basestring
except Exception:
STRING_TYPE = str
WRAPPER_TYPES = ["form", "collapsible-wrap"]
NOT_SET = type("NOT_SET", (), {"__bool__": lambda obj: False})()
OVERRIDE_VERSION = 1
template_key_pattern = re.compile(r"(\{.*?[^{0]*\})")
def _fill_schema_template_data(
template, template_data, required_keys=None, missing_keys=None
):
first = False
if required_keys is None:
first = True
required_keys = set()
missing_keys = set()
_template = []
default_values = {}
for item in template:
if isinstance(item, dict) and "__default_values__" in item:
default_values = item["__default_values__"]
else:
_template.append(item)
template = _template
for key, value in default_values.items():
if key not in template_data:
template_data[key] = value
if not template:
output = template
elif isinstance(template, list):
output = []
for item in template:
output.append(_fill_schema_template_data(
item, template_data, required_keys, missing_keys
))
elif isinstance(template, dict):
output = {}
for key, value in template.items():
output[key] = _fill_schema_template_data(
value, template_data, required_keys, missing_keys
)
elif isinstance(template, STRING_TYPE):
# TODO find much better way how to handle filling template data
for replacement_string in template_key_pattern.findall(template):
key = str(replacement_string[1:-1])
required_keys.add(key)
if key not in template_data:
missing_keys.add(key)
continue
value = template_data[key]
if replacement_string == template:
# Replace the value with value from templates data
# - with this is possible to set value with different type
template = value
else:
# Only replace the key in string
template = template.replace(replacement_string, value)
output = template
else:
output = template
if first and missing_keys:
raise SchemaTemplateMissingKeys(missing_keys, required_keys)
return output
def _fill_schema_template(child_data, schema_collection, schema_templates):
template_name = child_data["name"]
template = schema_templates.get(template_name)
if template is None:
if template_name in schema_collection:
raise KeyError((
"Schema \"{}\" is used as `schema_template`"
).format(template_name))
raise KeyError("Schema template \"{}\" was not found".format(
template_name
))
# Default value must be dictionary (NOT list)
# - empty list would not add any item if `template_data` are not filled
template_data = child_data.get("template_data") or {}
if isinstance(template_data, dict):
template_data = [template_data]
output = []
for single_template_data in template_data:
try:
filled_child = _fill_schema_template_data(
template, single_template_data
)
except SchemaTemplateMissingKeys as exc:
raise SchemaTemplateMissingKeys(
exc.missing_keys, exc.required_keys, template_name
)
for item in filled_child:
filled_item = _fill_inner_schemas(
item, schema_collection, schema_templates
)
if filled_item["type"] == "schema_template":
output.extend(_fill_schema_template(
filled_item, schema_collection, schema_templates
))
else:
output.append(filled_item)
return output
def _fill_inner_schemas(schema_data, schema_collection, schema_templates):
if schema_data["type"] == "schema":
raise ValueError("First item in schema data can't be schema.")
children_key = "children"
object_type_key = "object_type"
for item_key in (children_key, object_type_key):
children = schema_data.get(item_key)
if not children:
continue
if object_type_key == item_key:
if not isinstance(children, dict):
continue
children = [children]
new_children = []
for child in children:
child_type = child["type"]
if child_type == "schema":
schema_name = child["name"]
if schema_name not in schema_collection:
if schema_name in schema_templates:
raise KeyError((
"Schema template \"{}\" is used as `schema`"
).format(schema_name))
raise KeyError(
"Schema \"{}\" was not found".format(schema_name)
)
filled_child = _fill_inner_schemas(
schema_collection[schema_name],
schema_collection,
schema_templates
)
elif child_type == "schema_template":
for filled_child in _fill_schema_template(
child, schema_collection, schema_templates
):
new_children.append(filled_child)
continue
else:
filled_child = _fill_inner_schemas(
child, schema_collection, schema_templates
)
new_children.append(filled_child)
if item_key == object_type_key:
if len(new_children) != 1:
raise KeyError((
"Failed to fill object type with type: {} | name {}"
).format(
child_type, str(child.get("name"))
))
new_children = new_children[0]
schema_data[item_key] = new_children
return schema_data
# TODO reimplement logic inside entities
def validate_environment_groups_uniquenes(
schema_data, env_groups=None, keys=None
):
is_first = False
if env_groups is None:
is_first = True
env_groups = {}
keys = []
my_keys = copy.deepcopy(keys)
key = schema_data.get("key")
if key:
my_keys.append(key)
env_group_key = schema_data.get("env_group_key")
if env_group_key:
if env_group_key not in env_groups:
env_groups[env_group_key] = []
env_groups[env_group_key].append("/".join(my_keys))
children = schema_data.get("children")
if not children:
return
for child in children:
validate_environment_groups_uniquenes(
child, env_groups, copy.deepcopy(my_keys)
)
if is_first:
invalid = {}
for env_group_key, key_paths in env_groups.items():
if len(key_paths) > 1:
invalid[env_group_key] = key_paths
if invalid:
raise SchemaDuplicatedEnvGroupKeys(invalid)
def validate_schema(schema_data):
validate_environment_groups_uniquenes(schema_data)
def get_gui_schema(subfolder, main_schema_name):
dirpath = os.path.join(
os.path.dirname(__file__),
"schemas",
subfolder
)
loaded_schemas = {}
loaded_schema_templates = {}
for root, _, filenames in os.walk(dirpath):
for filename in filenames:
basename, ext = os.path.splitext(filename)
if ext != ".json":
continue
filepath = os.path.join(root, filename)
with open(filepath, "r") as json_stream:
try:
schema_data = json.load(json_stream)
except Exception as exc:
raise ValueError((
"Unable to parse JSON file {}\n{}"
).format(filepath, str(exc)))
if isinstance(schema_data, list):
loaded_schema_templates[basename] = schema_data
else:
loaded_schemas[basename] = schema_data
main_schema = _fill_inner_schemas(
loaded_schemas[main_schema_name],
loaded_schemas,
loaded_schema_templates
)
validate_schema(main_schema)
return main_schema
def get_studio_settings_schema():
return get_gui_schema("system_schema", "schema_main")
def get_project_settings_schema():
return get_gui_schema("projects_schema", "schema_main")
class OverrideStateItem:
"""Object used as item for `OverrideState` enum.
Used object to be able use exact object comparison and value comparisons.
"""
values = set()
def __init__(self, value, name):
self.name = name
if value in self.__class__.values:
raise ValueError(
"Implementation bug: Override State with same value as other."
)
self.__class__.values.add(value)
self.value = value
def __repr__(self):
return "<object {}> {} {}".format(
self.__class__.__name__, self.value, self.name
)
def __eq__(self, other):
"""Defines behavior for the equality operator, ==."""
if isinstance(other, OverrideStateItem):
return self.value == other.value
return self.value == other
def __gt__(self, other):
"""Defines behavior for the greater-than operator, >."""
if isinstance(other, OverrideStateItem):
return self.value > other.value
return self.value > other
def __lt__(self, other):
"""Defines behavior for the less-than operator, <."""
if isinstance(other, OverrideStateItem):
return self.value < other.value
return self.value < other
def __le__(self, other):
"""Defines behavior for the less-than-or-equal-to operator, <=."""
if isinstance(other, OverrideStateItem):
return self.value == other.value or self.value < other.value
return self.value == other or self.value < other
def __ge__(self, other):
"""Defines behavior for the greater-than-or-equal-to operator, >=."""
if isinstance(other, OverrideStateItem):
return self.value == other.value or self.value > other.value
return self.value == other or self.value > other
class OverrideState:
"""Enumeration of override states.
Each state have unique value.
Currently has 4 states:
- NOT_DEFINED - Initial state will raise an error if want to access
anything in entity.
- DEFAULTS - Entity cares only about default values. It is not
possible to set higher state if any entity does not have filled
default value.
- STUDIO - First layer of overrides. Hold only studio overriden values
that are applied on top of defaults.
- PROJECT - Second layer of overrides. Hold only project overrides that are
applied on top of defaults and studio overrides.
"""
NOT_DEFINED = OverrideStateItem(-1, "Not defined")
DEFAULTS = OverrideStateItem(0, "Defaults")
STUDIO = OverrideStateItem(1, "Studio overrides")
PROJECT = OverrideStateItem(2, "Project Overrides")

View file

@ -0,0 +1,470 @@
import copy
from . import (
BaseEntity,
EndpointEntity
)
from .lib import (
NOT_SET,
OverrideState
)
from .exceptions import (
DefaultsNotDefined,
StudioDefaultsNotDefined,
EntitySchemaError
)
class ListEntity(EndpointEntity):
schema_types = ["list"]
_default_label_wrap = {
"use_label_wrap": False,
"collapsible": True,
"collapsed": False
}
def __iter__(self):
for item in self.children:
yield item
def __bool__(self):
"""Returns true because len may return 0."""
return True
def __len__(self):
return len(self.children)
def __contains__(self, item):
if isinstance(item, BaseEntity):
for child_entity in self.children:
if child_entity.id == item.id:
return True
return False
for _item in self.value:
if item == _item:
return True
return False
def index(self, item):
if isinstance(item, BaseEntity):
for idx, child_entity in enumerate(self.children):
if child_entity.id == item.id:
return idx
else:
for idx, _item in enumerate(self.value):
if item == _item:
return idx
raise ValueError(
"{} is not in {}".format(item, self.__class__.__name__)
)
def append(self, item):
child_obj = self._add_new_item()
child_obj.set_override_state(self._override_state)
child_obj.set(item)
self.on_change()
def extend(self, items):
for item in items:
self.append(item)
def clear(self):
self.children.clear()
self.on_change()
def pop(self, idx):
item = self.children.pop(idx)
self.on_change()
return item
def remove(self, item):
for idx, child_obj in enumerate(self.children):
found = False
if isinstance(item, BaseEntity):
if child_obj is item:
found = True
elif child_obj.value == item:
found = True
if found:
self.pop(idx)
return
raise ValueError("ListEntity.remove(x): x not in ListEntity")
def insert(self, idx, item):
child_obj = self._add_new_item(idx)
child_obj.set_override_state(self._override_state)
child_obj.set(item)
self.on_change()
def _add_new_item(self, idx=None):
child_obj = self.create_schema_object(self.item_schema, self, True)
if idx is None:
self.children.append(child_obj)
else:
self.children.insert(idx, child_obj)
return child_obj
def add_new_item(self, idx=None):
child_obj = self._add_new_item(idx)
child_obj.set_override_state(self._override_state)
self.on_change()
return child_obj
def swap_items(self, item_1, item_2):
index_1 = self.index(item_1)
index_2 = self.index(item_2)
self.swap_indexes(index_1, index_2)
def swap_indexes(self, index_1, index_2):
children_len = len(self.children)
if index_1 > children_len or index_2 > children_len:
raise IndexError(
"{} index out of range".format(self.__class__.__name__)
)
self.children[index_1], self.children[index_2] = (
self.children[index_2], self.children[index_1]
)
self.on_change()
def _convert_to_valid_type(self, value):
if isinstance(value, (set, tuple)):
return list(value)
return NOT_SET
def _item_initalization(self):
self.valid_value_types = (list, )
self.children = []
self.value_on_not_set = []
self._ignore_child_changes = False
item_schema = self.schema_data["object_type"]
if not isinstance(item_schema, dict):
item_schema = {"type": item_schema}
self.item_schema = item_schema
if not self.group_item:
self.is_group = True
# Value that was set on set_override_state
self.initial_value = []
def schema_validations(self):
super(ListEntity, self).schema_validations()
if self.is_dynamic_item and self.use_label_wrap:
reason = (
"`ListWidget` can't have set `use_label_wrap` to True and"
" be used as widget at the same time."
)
raise EntitySchemaError(self, reason)
if self.use_label_wrap and not self.label:
reason = (
"`ListWidget` can't have set `use_label_wrap` to True and"
" not have set \"label\" key at the same time."
)
raise EntitySchemaError(self, reason)
for child_obj in self.children:
child_obj.schema_validations()
def get_child_path(self, child_obj):
result_idx = None
for idx, _child_obj in enumerate(self.children):
if _child_obj is child_obj:
result_idx = idx
break
if result_idx is None:
raise ValueError("Didn't found child {}".format(child_obj))
return "/".join([self.path, str(result_idx)])
def set(self, value):
new_value = self.convert_to_valid_type(value)
self.clear()
for item in new_value:
self.append(item)
def on_child_change(self, _child_entity):
if self._ignore_child_changes:
return
if self._override_state is OverrideState.STUDIO:
self._has_studio_override = True
elif self._override_state is OverrideState.PROJECT:
self._has_project_override = True
self.on_change()
def set_override_state(self, state):
# Trigger override state change of root if is not same
if self.root_item.override_state is not state:
self.root_item.set_override_state(state)
return
self._override_state = state
while self.children:
self.children.pop(0)
# Ignore if is dynamic item and use default in that case
if not self.is_dynamic_item and not self.is_in_dynamic_item:
if state > OverrideState.DEFAULTS:
if not self.has_default_value:
raise DefaultsNotDefined(self)
elif state > OverrideState.STUDIO:
if not self.had_studio_override:
raise StudioDefaultsNotDefined(self)
value = NOT_SET
if self._override_state is OverrideState.PROJECT:
if self.had_project_override:
value = self._project_override_value
self._has_project_override = self.had_project_override
if value is NOT_SET or self._override_state is OverrideState.STUDIO:
if self.had_studio_override:
value = self._studio_override_value
self._has_studio_override = self.had_studio_override
if value is NOT_SET or self._override_state is OverrideState.DEFAULTS:
if self.has_default_value:
value = self._default_value
else:
value = self.value_on_not_set
for item in value:
child_obj = self._add_new_item()
child_obj.update_default_value(item)
if self._override_state is OverrideState.PROJECT:
if self.had_project_override:
child_obj.update_project_value(item)
elif self.had_studio_override:
child_obj.update_studio_value(item)
elif self._override_state is OverrideState.STUDIO:
if self.had_studio_override:
child_obj.update_studio_value(item)
for child_obj in self.children:
child_obj.set_override_state(self._override_state)
self.initial_value = self.settings_value()
@property
def value(self):
output = []
for child_obj in self.children:
output.append(child_obj.value)
return output
@property
def has_unsaved_changes(self):
if self._override_state is OverrideState.NOT_DEFINED:
return False
if self._override_state is OverrideState.DEFAULTS:
if not self.has_default_value:
return True
elif self._override_state is OverrideState.STUDIO:
if self.had_studio_override != self._has_studio_override:
return True
if not self._has_studio_override and not self.has_default_value:
return True
elif self._override_state is OverrideState.PROJECT:
if self.had_project_override != self._has_project_override:
return True
if (
not self._has_project_override
and not self._has_studio_override
and not self.has_default_value
):
return True
if self._child_has_unsaved_changes:
return True
if self.settings_value() != self.initial_value:
return True
return False
@property
def has_studio_override(self):
if self._override_state >= OverrideState.STUDIO:
return (
self._has_studio_override
or self._child_has_studio_override
)
return False
@property
def has_project_override(self):
if self._override_state >= OverrideState.PROJECT:
return (
self._has_project_override
or self._child_has_project_override
)
return False
@property
def _child_has_unsaved_changes(self):
for child_obj in self.children:
if child_obj.has_unsaved_changes:
return True
return False
@property
def _child_has_studio_override(self):
if self._override_state >= OverrideState.STUDIO:
for child_obj in self.children:
if child_obj.has_studio_override:
return True
return False
@property
def _child_has_project_override(self):
if self._override_state is OverrideState.PROJECT:
for child_obj in self.children:
if child_obj.has_project_override:
return True
return False
def _settings_value(self):
output = []
for child_obj in self.children:
output.append(child_obj.settings_value())
return output
def _discard_changes(self, on_change_trigger):
if self._override_state is OverrideState.NOT_DEFINED:
return
not_set = object()
value = not_set
if (
self._override_state >= OverrideState.PROJECT
and self.had_project_override
):
value = copy.deepcopy(self._project_override_value)
if (
value is not_set
and self._override_state >= OverrideState.STUDIO
and self.had_studio_override
):
value = copy.deepcopy(self._studio_override_value)
if value is not_set and self._override_state >= OverrideState.DEFAULTS:
if self.has_default_value:
value = copy.deepcopy(self._default_value)
else:
value = copy.deepcopy(self.value_on_not_set)
if value is not_set:
raise NotImplementedError("BUG: Unexcpected part of code.")
self._ignore_child_changes = True
while self.children:
self.children.pop(0)
for item in value:
child_obj = self._add_new_item()
child_obj.update_default_value(item)
if self._override_state is OverrideState.PROJECT:
if self.had_project_override:
child_obj.update_project_value(item)
elif self.had_studio_override:
child_obj.update_studio_value(item)
elif self._override_state is OverrideState.STUDIO:
if self.had_studio_override:
child_obj.update_studio_value(item)
child_obj.set_override_state(self._override_state)
if self._override_state >= OverrideState.PROJECT:
self._has_project_override = self.had_project_override
if self._override_state >= OverrideState.STUDIO:
self._has_studio_override = self.had_studio_override
self._ignore_child_changes = False
on_change_trigger.append(self.on_change)
def _add_to_studio_default(self, _on_change_trigger):
self._has_studio_override = True
self.on_change()
def _remove_from_studio_default(self, on_change_trigger):
if self._override_state is not OverrideState.STUDIO:
return
value = self._default_value
if value is NOT_SET:
value = self.value_on_not_set
self._ignore_child_changes = True
while self.children:
self.children.pop(0)
for item in value:
child_obj = self._add_new_item()
child_obj.update_default_value(item)
child_obj.set_override_state(self._override_state)
self._ignore_child_changes = False
self._has_studio_override = False
on_change_trigger.append(self.on_change)
def _add_to_project_override(self, _on_change_trigger):
self._has_project_override = True
self.on_change()
def _remove_from_project_override(self, on_change_trigger):
if self._override_state is not OverrideState.PROJECT:
return
if not self.has_project_override:
return
if self._has_studio_override:
value = self._studio_override_value
elif self.has_default_value:
value = self._default_value
else:
value = self.value_on_not_set
self._ignore_child_changes = True
while self.children:
self.children.pop(0)
for item in value:
child_obj = self._add_new_item()
child_obj.update_default_value(item)
if self._has_studio_override:
child_obj.update_studio_value(item)
child_obj.set_override_state(self._override_state)
self._ignore_child_changes = False
self._has_project_override = False
on_change_trigger.append(self.on_change)
def reset_callbacks(self):
super(ListEntity, self).reset_callbacks()
for child_entity in self.children:
child_entity.reset_callbacks()

View file

@ -0,0 +1,739 @@
import os
import json
import copy
import inspect
from abc import abstractmethod
from .base_entity import BaseItemEntity
from .lib import (
NOT_SET,
WRAPPER_TYPES,
OverrideState,
get_studio_settings_schema,
get_project_settings_schema
)
from .exceptions import (
SchemaError,
InvalidKeySymbols
)
from openpype.settings.constants import (
SYSTEM_SETTINGS_KEY,
PROJECT_SETTINGS_KEY,
PROJECT_ANATOMY_KEY,
KEY_REGEX
)
from openpype.settings.lib import (
DEFAULTS_DIR,
get_default_settings,
get_studio_system_settings_overrides,
save_studio_settings,
get_studio_project_settings_overrides,
get_studio_project_anatomy_overrides,
get_project_settings_overrides,
get_project_anatomy_overrides,
save_project_settings,
save_project_anatomy,
find_environments,
apply_overrides
)
class RootEntity(BaseItemEntity):
"""Abstract class for root entities.
Root entity is top hierarchy entity without parent. Should care about
saving and must have access to all entities in it's scope.
"""
schema_types = ["root"]
def __init__(self, schema_data, reset):
super(RootEntity, self).__init__(schema_data)
self._item_initalization()
if reset:
self.reset()
@property
def override_state(self):
"""Current OverrideState."""
return self._override_state
@abstractmethod
def reset(self):
"""Reset values and entities to initial state.
Reload settings and entities should reset their changes or be
recreated.
"""
pass
def __getitem__(self, key):
return self.non_gui_children[key]
def __setitem__(self, key, value):
self.non_gui_children[key].set(value)
def __iter__(self):
for key in self.keys():
yield key
def get(self, key, default=None):
return self.non_gui_children.get(key, default)
def set(self, value):
"""Set value."""
new_value = self.convert_to_valid_type(value)
for _key, _value in new_value.items():
self.non_gui_children[_key].set(_value)
def keys(self):
return self.non_gui_children.keys()
def values(self):
return self.non_gui_children.values()
def items(self):
return self.non_gui_children.items()
def _add_children(self, schema_data, first=True):
added_children = []
for children_schema in schema_data["children"]:
if children_schema["type"] in WRAPPER_TYPES:
_children_schema = copy.deepcopy(children_schema)
wrapper_children = self._add_children(
children_schema["children"], False
)
_children_schema["children"] = wrapper_children
added_children.append(_children_schema)
continue
child_obj = self.create_schema_object(children_schema, self)
self.children.append(child_obj)
added_children.append(child_obj)
if isinstance(child_obj, self._gui_types):
continue
if child_obj.key in self.non_gui_children:
raise KeyError("Duplicated key \"{}\"".format(child_obj.key))
self.non_gui_children[child_obj.key] = child_obj
if not first:
return added_children
for child_obj in added_children:
self.gui_layout.append(child_obj)
def _item_initalization(self):
# Store `self` to `root_item` for children entities
self.root_item = self
self._loaded_types = None
self._gui_types = None
# Children are stored by key as keys are immutable and are defined by
# schema
self.valid_value_types = (dict, )
self.children = []
self.non_gui_children = {}
self.gui_layout = []
self._add_children(self.schema_data)
self.schema_validations()
def schema_validations(self):
for child_entity in self.children:
if child_entity.is_group:
reason = (
"Root entity \"{}\" has child with `is_group`"
" attribute set to True but root can't save overrides."
).format(self.__class__.__name__)
raise SchemaError(reason)
child_entity.schema_validations()
for key in self.non_gui_children.keys():
if not KEY_REGEX.match(key):
raise InvalidKeySymbols(self.path, key)
def get_entity_from_path(self, path):
"""Return system settings entity."""
raise NotImplementedError((
"Method `get_entity_from_path` not available for \"{}\""
).format(self.__class__.__name__))
def create_schema_object(self, schema_data, *args, **kwargs):
"""Create entity by entered schema data.
Available entities are loaded on first run. Children entities can call
this method.
"""
if self._loaded_types is None:
# Load available entities
from openpype.settings import entities
# Define known abstract classes
known_abstract_classes = (
entities.BaseEntity,
entities.BaseItemEntity,
entities.ItemEntity,
entities.EndpointEntity,
entities.InputEntity,
entities.BaseEnumEntity
)
self._loaded_types = {}
_gui_types = []
for attr in dir(entities):
item = getattr(entities, attr)
# Filter classes
if not inspect.isclass(item):
continue
# Skip classes that do not inherit from BaseEntity
if not issubclass(item, entities.BaseEntity):
continue
# Skip class that is abstract by design
if item in known_abstract_classes:
continue
if inspect.isabstract(item):
# Create an object to get crash and get traceback
item()
# Backwards compatibility
# Single entity may have multiple schema types
for schema_type in item.schema_types:
self._loaded_types[schema_type] = item
if item.gui_type:
_gui_types.append(item)
self._gui_types = tuple(_gui_types)
klass = self._loaded_types.get(schema_data["type"])
if not klass:
raise KeyError("Unknown type \"{}\"".format(schema_data["type"]))
return klass(schema_data, *args, **kwargs)
def set_override_state(self, state):
"""Set override state and trigger it on children.
Method will discard all changes in hierarchy and use values, metadata
and all kind of values for defined state.
Args:
state (OverrideState): State to which should be data changed.
"""
self._override_state = state
for child_obj in self.non_gui_children.values():
child_obj.set_override_state(state)
def on_change(self):
"""Trigger callbacks on change."""
for callback in self.on_change_callbacks:
callback()
def on_child_change(self, _child_entity):
"""Whan any children has changed."""
self.on_change()
def get_child_path(self, child_entity):
"""Return path of children entity"""
for key, _child_entity in self.non_gui_children.items():
if _child_entity is child_entity:
return key
raise ValueError("Didn't found child {}".format(child_entity))
@property
def value(self):
"""Value for current override state without metadata."""
output = {}
for key, child_obj in self.non_gui_children.items():
output[key] = child_obj.value
return output
def settings_value(self):
"""Value for current override state with metadata.
This is what should be stored on save method.
"""
if self._override_state is OverrideState.NOT_DEFINED:
return NOT_SET
if self._override_state is not OverrideState.DEFAULTS:
output = {}
for key, child_obj in self.non_gui_children.items():
value = child_obj.settings_value()
if value is not NOT_SET:
output[key] = value
return output
output = {}
for key, child_obj in self.non_gui_children.items():
child_value = child_obj.settings_value()
if not child_obj.is_file and not child_obj.file_item:
for _key, _value in child_value.items():
new_key = "/".join([key, _key])
output[new_key] = _value
else:
output[key] = child_value
return output
@property
def has_studio_override(self):
"""Any children has studio override.
Return's relevant data only if override state is STUDIO or PROJECT.
Returns:
bool: True if any children has studio overrides.
"""
if self._override_state >= OverrideState.STUDIO:
for child_obj in self.non_gui_children.values():
if child_obj.has_studio_override:
return True
return False
@property
def has_project_override(self):
"""Any children has project override.
Return's relevant data only if override state is PROJECT.
Returns:
bool: True if any children has project overrides.
"""
if self._override_state >= OverrideState.PROJECT:
for child_obj in self.non_gui_children.values():
if child_obj.has_project_override:
return True
return False
@property
def has_unsaved_changes(self):
"""Entity contain unsaved changes.
Root on it's own can't have any modifications so looks only on
children.
Returns:
bool: True if has unsaved changes.
"""
for child_obj in self.non_gui_children.values():
if child_obj.has_unsaved_changes:
return True
return False
def _discard_changes(self, on_change_trigger):
"""Implementation of abstract method only trigger children callback."""
for child_obj in self.non_gui_children.values():
child_obj.discard_changes(on_change_trigger)
def _add_to_studio_default(self, *args, **kwargs):
"""Implementation of abstract method only trigger children callback."""
for child_obj in self.non_gui_children.values():
child_obj.add_to_studio_default(*args, **kwargs)
def _remove_from_studio_default(self, on_change_trigger):
"""Implementation of abstract method only trigger children callback."""
for child_obj in self.non_gui_children.values():
child_obj.remove_from_studio_default(on_change_trigger)
def _add_to_project_override(self, *args, **kwargs):
"""Implementation of abstract method only trigger children callback."""
for child_obj in self.non_gui_children.values():
child_obj.add_to_project_override(*args, **kwargs)
def _remove_from_project_override(self, on_change_trigger):
"""Implementation of abstract method only trigger children callback."""
for child_obj in self.non_gui_children.values():
child_obj.remove_from_project_override(on_change_trigger)
def save(self):
"""Save values for current override state.
Values are stored with current values and modifications.
"""
if self._override_state is OverrideState.NOT_DEFINED:
raise ValueError(
"Can't save if override state is set to NOT_DEFINED"
)
if self._override_state is OverrideState.DEFAULTS:
self._save_default_values()
elif self._override_state is OverrideState.STUDIO:
self._save_studio_values()
elif self._override_state is OverrideState.PROJECT:
self._save_project_values()
# Trigger reset to reload entities
self.reset()
@abstractmethod
def defaults_dir(self):
"""Abstract method to return directory path to defaults.
Implementation of `_save_default_values` requires defaults dir where
default data will be stored.
"""
pass
@abstractmethod
def _validate_defaults_to_save(self, value):
"""Validate default values before save."""
pass
def _save_default_values(self):
"""Save default values.
Do not call this method, always use `save`. Manually called method
won't save current values as defaults if override state is not set to
DEFAULTS.
"""
settings_value = self.settings_value()
self._validate_defaults_to_save(settings_value)
defaults_dir = self.defaults_dir()
for file_path, value in settings_value.items():
subpath = file_path + ".json"
output_path = os.path.join(defaults_dir, subpath)
dirpath = os.path.dirname(output_path)
if not os.path.exists(dirpath):
os.makedirs(dirpath)
self.log.debug("Saving data to: {}\n{}".format(subpath, value))
with open(output_path, "w") as file_stream:
json.dump(value, file_stream, indent=4)
@abstractmethod
def _save_studio_values(self):
"""Save studio override values."""
pass
@abstractmethod
def _save_project_values(self):
"""Save project override values."""
pass
def is_in_defaults_state(self):
"""Api callback to check if current state is DEFAULTS."""
return self._override_state is OverrideState.DEFAULTS
def is_in_studio_state(self):
"""Api callback to check if current state is STUDIO."""
return self._override_state is OverrideState.STUDIO
def is_in_project_state(self):
"""Api callback to check if current state is PROJECT."""
return self._override_state is OverrideState.PROJECT
def set_defaults_state(self):
"""Change override state to DEFAULTS."""
self.set_override_state(OverrideState.DEFAULTS)
def set_studio_state(self):
"""Change override state to STUDIO."""
self.set_override_state(OverrideState.STUDIO)
def set_project_state(self):
"""Change override state to PROJECT."""
self.set_override_state(OverrideState.PROJECT)
class SystemSettings(RootEntity):
"""Root entity for system settings.
Allows to modify system settings via entity system and loaded schemas.
Args:
set_studio_state (bool): Set studio values on initialization. By
default is set to True.
reset (bool): Reset values on initialization. By default is set to
True.
schema_data (dict): Pass schema data to entity. This is for development
and debugging purposes.
"""
def __init__(
self, set_studio_state=True, reset=True, schema_data=None
):
if schema_data is None:
# Load system schemas
schema_data = get_studio_settings_schema()
super(SystemSettings, self).__init__(schema_data, reset)
if set_studio_state:
self.set_studio_state()
def _reset_values(self):
default_value = get_default_settings()[SYSTEM_SETTINGS_KEY]
for key, child_obj in self.non_gui_children.items():
value = default_value.get(key, NOT_SET)
child_obj.update_default_value(value)
studio_overrides = get_studio_system_settings_overrides()
for key, child_obj in self.non_gui_children.items():
value = studio_overrides.get(key, NOT_SET)
child_obj.update_studio_value(value)
def reset(self, new_state=None):
"""Discard changes and reset entit's values.
Reload default values and studio override values and update entities.
Args:
new_state (OverrideState): It is possible to change override state
during reset. Current state is used if not defined.
"""
if new_state is None:
new_state = self._override_state
if new_state is OverrideState.NOT_DEFINED:
new_state = OverrideState.DEFAULTS
if new_state is OverrideState.PROJECT:
raise ValueError("System settings can't store poject overrides.")
self._reset_values()
self.set_override_state(new_state)
def defaults_dir(self):
"""Path to defaults directory.
Implementation of abstract method.
"""
return os.path.join(DEFAULTS_DIR, SYSTEM_SETTINGS_KEY)
def _save_studio_values(self):
settings_value = self.settings_value()
self._validate_duplicated_env_group(settings_value)
self.log.debug("Saving system settings: {}".format(
json.dumps(settings_value, indent=4)
))
save_studio_settings(settings_value)
def _validate_defaults_to_save(self, value):
"""Valiations of default values before save."""
self._validate_duplicated_env_group(value)
def _validate_duplicated_env_group(self, value, override_state=None):
""" Validate duplicated environment groups.
Raises:
DuplicatedEnvGroups: When value contain duplicated env groups.
"""
value = copy.deepcopy(value)
if override_state is None:
override_state = self._override_state
if override_state is OverrideState.STUDIO:
default_values = get_default_settings()[SYSTEM_SETTINGS_KEY]
final_value = apply_overrides(default_values, value)
else:
final_value = value
# Check if final_value contain duplicated environment groups
find_environments(final_value)
def _save_project_values(self):
"""System settings can't have project overrides.
Raises:
ValueError: Raise when called as entity can't use or store project
overrides.
"""
raise ValueError("System settings can't save project overrides.")
class ProjectSettings(RootEntity):
"""Root entity for project settings.
Allows to modify project settings via entity system and loaded schemas.
Args:
project_name (str): Project name which overrides will be loaded.
Use `None` to modify studio defaults.
change_state (bool): Set values on initialization. By
default is set to True.
reset (bool): Reset values on initialization. By default is set to
True.
schema_data (dict): Pass schema data to entity. This is for development
and debugging purposes.
"""
def __init__(
self,
project_name=None,
change_state=True,
reset=True,
schema_data=None
):
self._project_name = project_name
self._system_settings_entity = None
if schema_data is None:
# Load system schemas
schema_data = get_project_settings_schema()
super(ProjectSettings, self).__init__(schema_data, reset)
if change_state:
if self.project_name is None:
self.set_studio_state()
else:
self.set_project_state()
@property
def project_name(self):
return self._project_name
@project_name.setter
def project_name(self, project_name):
self.change_project(project_name)
@property
def system_settings_entity(self):
output = self._system_settings_entity
if output is None:
output = SystemSettings(set_studio_state=False)
self._system_settings_entity = output
if self.override_state is OverrideState.DEFAULTS:
if output.override_state is not OverrideState.DEFAULTS:
output.set_defaults_state()
elif self.override_state > OverrideState.DEFAULTS:
if output.override_state <= OverrideState.DEFAULTS:
try:
output.set_studio_state()
except Exception:
output.set_defaults_state()
return output
def get_entity_from_path(self, path):
path_parts = path.split("/")
first_part = path_parts[0]
# TODO replace with constants
if first_part == "system_settings":
output = self.system_settings_entity
path_parts.pop(0)
else:
output = self
if first_part == "project_settings":
path_parts.pop(0)
for path_part in path_parts:
output = output[path_part]
return output
def change_project(self, project_name):
if project_name == self._project_name:
return
self._project_name = project_name
if project_name is None:
self.set_studio_state()
return
project_override_value = {
PROJECT_SETTINGS_KEY: get_project_settings_overrides(project_name),
PROJECT_ANATOMY_KEY: get_project_anatomy_overrides(project_name)
}
for key, child_obj in self.non_gui_children.items():
value = project_override_value.get(key, NOT_SET)
child_obj.update_project_value(value)
self.set_project_state()
def _reset_values(self):
default_values = {
PROJECT_SETTINGS_KEY: get_default_settings()[PROJECT_SETTINGS_KEY],
PROJECT_ANATOMY_KEY: get_default_settings()[PROJECT_ANATOMY_KEY]
}
for key, child_obj in self.non_gui_children.items():
value = default_values.get(key, NOT_SET)
child_obj.update_default_value(value)
studio_overrides = {
PROJECT_SETTINGS_KEY: get_studio_project_settings_overrides(),
PROJECT_ANATOMY_KEY: get_studio_project_anatomy_overrides()
}
for key, child_obj in self.non_gui_children.items():
value = studio_overrides.get(key, NOT_SET)
child_obj.update_studio_value(value)
if not self.project_name:
return
project_name = self.project_name
project_override_value = {
PROJECT_SETTINGS_KEY: get_project_settings_overrides(project_name),
PROJECT_ANATOMY_KEY: get_project_anatomy_overrides(project_name)
}
for key, child_obj in self.non_gui_children.items():
value = project_override_value.get(key, NOT_SET)
child_obj.update_project_value(value)
def reset(self, new_state=None):
"""Discard changes and reset entit's values.
Reload default values and studio override values and update entities.
Args:
new_state (OverrideState): It is possible to change override state
during reset. Current state is used if not defined.
"""
if new_state is None:
new_state = self._override_state
if new_state is OverrideState.NOT_DEFINED:
new_state = OverrideState.DEFAULTS
self._system_settings_entity = None
self._reset_values()
self.set_override_state(new_state)
def defaults_dir(self):
"""Path to defaults directory.
Implementation of abstract method.
"""
return DEFAULTS_DIR
def _save_studio_values(self):
settings_value = self.settings_value()
self._validate_values_to_save(settings_value)
self.log.debug("Saving project settings: {}".format(
json.dumps(settings_value, indent=4)
))
project_settings = settings_value.get(PROJECT_SETTINGS_KEY) or {}
project_anatomy = settings_value.get(PROJECT_ANATOMY_KEY) or {}
save_project_settings(self.project_name, project_settings)
save_project_anatomy(self.project_name, project_anatomy)
def _validate_defaults_to_save(self, value):
"""Valiations of default values before save."""
pass
def _validate_values_to_save(self, value):
pass
def _save_project_values(self):
"""Project overrides are saved same ways as studio overrides."""
self._save_studio_values()

View file

@ -0,0 +1,494 @@
# Creating GUI schemas
## Basic rules
- configurations does not define GUI, but GUI defines configurations!
- output is always json (yaml is not needed for anatomy templates anymore)
- GUI schema has multiple input types, all inputs are represented by a dictionary
- each input may have "input modifiers" (keys in dictionary) that are required or optional
- only required modifier for all input items is key `"type"` which says what type of item it is
- there are special keys across all inputs
- `"is_file"` - this key is for storing openpype defaults in `openpype` repo
- reasons of existence: developing new schemas does not require to create defaults manually
- key is validated, must be once in hierarchy else it won't be possible to store openpype defaults
- `"is_group"` - define that all values under key in hierarchy will be overriden if any value is modified, this information is also stored to overrides
- this keys is not allowed for all inputs as they may have not reason for that
- key is validated, can be only once in hierarchy but is not required
- currently there are `system configurations` and `project configurations`
## Inner schema
- GUI schemas are huge json files, to be able to split whole configuration into multiple schema there's type `schema`
- system configuration schemas are stored in `~/tools/settings/settings/gui_schemas/system_schema/` and project configurations in `~/tools/settings/settings/gui_schemas/projects_schema/`
- each schema name is filename of json file except extension (without ".json")
- if content is dictionary content will be used as `schema` else will be used as `schema_template`
### schema
- can have only key `"children"` which is list of strings, each string should represent another schema (order matters) string represebts name of the schema
- will just paste schemas from other schema file in order of "children" list
```
{
"type": "schema",
"name": "my_schema_name"
}
```
### schema_template
- allows to define schema "templates" to not duplicate same content multiple times
```javascript
// EXAMPLE json file content (filename: example_template.json)
[
{
"__default_values__": {
"multipath_executables": true
}
}, {
"type": "raw-json",
"label": "{host_label} Environments",
"key": "{host_name}_environments",
"env_group_key": "{host_name}"
}, {
"type": "path",
"key": "{host_name}_executables",
"label": "{host_label} - Full paths to executables",
"multiplatform": "{multipath_executables}",
"multipath": true
}
]
```
```javascript
// EXAMPLE usage of the template in schema
{
"type": "dict",
"key": "schema_template_examples",
"label": "Schema template examples",
"children": [
{
"type": "schema_template",
// filename of template (example_template.json)
"name": "example_template",
"template_data": {
"host_label": "Maya 2019",
"host_name": "maya_2019",
"multipath_executables": false
}
}, {
"type": "schema_template",
"name": "example_template",
"template_data": {
"host_label": "Maya 2020",
"host_name": "maya_2020"
}
}
]
}
```
- item in schema mush contain `"type"` and `"name"` keys but it is also expected that `"template_data"` will be entered too
- all items in the list, except `__default_values__`, will replace `schema_template` item in schema
- template may contain another template or schema
- it is expected that schema template will have unfilled fields as in example
- unfilled fields are allowed only in values of schema dictionary
```javascript
{
...
// Allowed
"key": "{to_fill}"
...
// Not allowed
"{to_fill}": "value"
...
}
```
- Unfilled fields can be also used for non string values, in that case value must contain only one key and value for fill must contain right type.
```javascript
{
...
// Allowed
"multiplatform": "{executable_multiplatform}"
...
// Not allowed
"multiplatform": "{executable_multiplatform}_enhanced_string"
...
}
```
- It is possible to define default values for unfilled fields to do so one of items in list must be dictionary with key `"__default_values__"` and value as dictionary with default key: values (as in example above).
## Basic Dictionary inputs
- these inputs wraps another inputs into {key: value} relation
## dict
- this is another dictionary input wrapping more inputs but visually makes them different
- item may be used as widget (in `list` or `dict-modifiable`)
- in that case the only key modifier is `children` which is list of it's keys
- USAGE: e.g. List of dictionaries where each dictionary have same structure.
- item may be with or without `"label"` if is not used as widget
- required keys are `"key"` under which will be stored
- without label it is just wrap item holding `"key"`
- can't have `"is_group"` key set to True as it breaks visual override showing
- if `"label"` is entetered there which will be shown in GUI
- item with label can be collapsible
- that can be set with key `"collapsible"` as `True`/`False` (Default: `True`)
- with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`)
- it is possible to add darker background with `"highlight_content"` (Default: `False`)
- darker background has limits of maximum applies after 3-4 nested highlighted items there is not difference in the color
- output is dictionary `{the "key": children values}`
```
# Example
{
"key": "applications",
"type": "dict",
"label": "Applications",
"collapsible": true,
"highlight_content": true,
"is_group": true,
"is_file": true,
"children": [
...ITEMS...
]
}
# Without label
{
"type": "dict",
"key": "global",
"children": [
...ITEMS...
]
}
# When used as widget
{
"type": "list",
"key": "profiles",
"label": "Profiles",
"object_type": {
"type": "dict",
"children": [
{
"key": "families",
"label": "Families",
"type": "list",
"object_type": "text"
}, {
"key": "hosts",
"label": "Hosts",
"type": "list",
"object_type": "text"
}
...
]
}
}
```
## Inputs for setting any kind of value (`Pure` inputs)
- all these input must have defined `"key"` under which will be stored and `"label"` which will be shown next to input
- unless they are used in different types of inputs (later) "as widgets" in that case `"key"` and `"label"` are not required as there is not place where to set them
### boolean
- simple checkbox, nothing more to set
```
{
"type": "boolean",
"key": "my_boolean_key",
"label": "Do you want to use Pype?"
}
```
### number
- number input, can be used for both integer and float
- key `"decimal"` defines how many decimal places will be used, 0 is for integer input (Default: `0`)
- key `"minimum"` as minimum allowed number to enter (Default: `-99999`)
- key `"maxium"` as maximum allowed number to enter (Default: `99999`)
```
{
"type": "number",
"key": "fps",
"label": "Frame rate (FPS)"
"decimal": 2,
"minimum": 1,
"maximum": 300000
}
```
### text
- simple text input
- key `"multiline"` allows to enter multiple lines of text (Default: `False`)
- key `"placeholder"` allows to show text inside input when is empty (Default: `None`)
```
{
"type": "text",
"key": "deadline_pool",
"label": "Deadline pool"
}
```
### path-input
- enhanced text input
- does not allow to enter backslash, is auto-converted to forward slash
- may be added another validations, like do not allow end path with slash
- this input is implemented to add additional features to text input
- this is meant to be used in proxy input `path-widget`
- DO NOT USE this input in schema please
### raw-json
- a little bit enhanced text input for raw json
- has validations of json format
- empty value is invalid value, always must be json serializable
- valid value types are list `[]` and dictionary `{}`
- schema also defines valid value type
- by default it is dictionary
- to be able use list it is required to define `is_list` to `true`
```
{
"type": "raw-json",
"key": "profiles",
"label": "Extract Review profiles",
"is_list": true
}
```
### enum
- returns value of single on multiple items from predefined values
- multiselection can be allowed with setting key `"multiselection"` to `True` (Default: `False`)
- values are defined under value of key `"enum_items"` as list
- each item in list is simple dictionary where value is label and key is value which will be stored
- should be possible to enter single dictionary if order of items doesn't matter
```
{
"key": "tags",
"label": "Tags",
"type": "enum",
"multiselection": true,
"enum_items": [
{"burnin": "Add burnins"},
{"ftrackreview": "Add to Ftrack"},
{"delete": "Delete output"},
{"slate-frame": "Add slate frame"},
{"no-hnadles": "Skip handle frames"}
]
}
```
## Inputs for setting value using Pure inputs
- these inputs also have required `"key"`
- attribute `"label"` is required in few conditions
- when item is marked `as_group` or when `use_label_wrap`
- they use Pure inputs "as widgets"
### list
- output is list
- items can be added and removed
- items in list must be the same type
- to wrap item in collapsible widget with label on top set `use_label_wrap` to `True`
- when this is used `collapsible` and `collapsed` can be set (same as `dict` item does)
- type of items is defined with key `"object_type"`
- there are 2 possible ways how to set the type:
1.) dictionary with item modifiers (`number` input has `minimum`, `maximum` and `decimals`) in that case item type must be set as value of `"type"` (example below)
2.) item type name as string without modifiers (e.g. `text`)
1.) with item modifiers
```
{
"type": "list",
"key": "exclude_ports",
"label": "Exclude ports",
"object_type": {
"type": "number", # number item type
"minimum": 1, # minimum modifier
"maximum": 65535 # maximum modifier
}
}
```
2.) without modifiers
```
{
"type": "list",
"key": "exclude_ports",
"label": "Exclude ports",
"object_type": "text"
}
```
### dict-modifiable
- one of dictionary inputs, this is only used as value input
- items in this input can be removed and added same way as in `list` input
- value items in dictionary must be the same type
- type of items is defined with key `"object_type"`
- required keys may be defined under `"required_keys"`
- required keys must be defined as a list (e.g. `["key_1"]`) and are moved to the top
- these keys can't be removed or edited (it is possible to edit label if item is collapsible)
- there are 2 possible ways how to set the type:
1.) dictionary with item modifiers (`number` input has `minimum`, `maximum` and `decimals`) in that case item type must be set as value of `"type"` (example below)
2.) item type name as string without modifiers (e.g. `text`)
- this input can be collapsible
- that can be set with key `"collapsible"` as `True`/`False` (Default: `True`)
- with key `"collapsed"` as `True`/`False` can be set that is collapsed when GUI is opened (Default: `False`)
1.) with item modifiers
```
{
"type": "dict-modifiable",
"object_type": {
"type": "number",
"minimum": 0,
"maximum": 300
},
"is_group": true,
"key": "templates_mapping",
"label": "Muster - Templates mapping",
"is_file": true
}
```
2.) without modifiers
```
{
"type": "dict-modifiable",
"object_type": "text",
"is_group": true,
"key": "templates_mapping",
"label": "Muster - Templates mapping",
"is_file": true
}
```
### path-widget
- input for paths, use `path-input` internally
- has 2 input modifiers `"multiplatform"` and `"multipath"`
- `"multiplatform"` - adds `"windows"`, `"linux"` and `"darwin"` path inputs result is dictionary
- `"multipath"` - it is possible to enter multiple paths
- if both are enabled result is dictionary with lists
```
{
"type": "path",
"key": "ffmpeg_path",
"label": "FFmpeg path",
"multiplatform": true,
"multipath": true
}
```
### list-strict
- input for strict number of items in list
- each child item can be different type with different possible modifiers
- it is possible to display them in horizontal or vertical layout
- key `"horizontal"` as `True`/`False` (Default: `True`)
- each child may have defined `"label"` which is shown next to input
- label does not reflect modifications or overrides (TODO)
- children item are defined under key `"object_types"` which is list of dictionaries
- key `"children"` is not used because is used for hierarchy validations in schema
- USAGE: For colors, transformations, etc. Custom number and different modifiers
give ability to define if color is HUE or RGB, 0-255, 0-1, 0-100 etc.
```
{
"type": "list-strict",
"key": "color",
"label": "Color",
"object_types": [
{
"label": "Red",
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
}, {
"label": "Green",
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
}, {
"label": "Blue",
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
}, {
"label": "Alpha",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 6
}
]
}
```
## Noninteractive widgets
- have nothing to do with data
### label
- add label with note or explanations
- it is possible to use html tags inside the label
```
{
"type": "label",
"label": "<span style=\"color:#FF0000\";>RED LABEL:</span> Normal label"
}
```
### splitter
- visual splitter of items (more divider than splitter)
```
{
"type": "splitter"
}
```
## Proxy wrappers
- should wraps multiple inputs only visually
- these does not have `"key"` key and do not allow to have `"is_file"` or `"is_group"` modifiers enabled
- can't be used as widget (first item in e.g. `list`, `dict-modifiable`, etc.)
### form
- wraps inputs into form look layout
- should be used only for Pure inputs
```
{
"type": "dict-form",
"children": [
{
"type": "text",
"key": "deadline_department",
"label": "Deadline apartment"
}, {
"type": "number",
"key": "deadline_priority",
"label": "Deadline priority"
}, {
...
}
]
}
```
### collapsible-wrap
- wraps inputs into collapsible widget
- looks like `dict` but does not hold `"key"`
- should be used only for Pure inputs
```
{
"type": "collapsible-wrap",
"label": "Collapsible example"
"children": [
{
"type": "text",
"key": "_example_input_collapsible",
"label": "Example input in collapsible wrapper"
}, {
...
}
]
}

View file

@ -0,0 +1,104 @@
{
"key": "project",
"type": "dict",
"children": [
{
"type": "anatomy",
"key": "project_anatomy",
"label": "Anatomy",
"children": [
{
"key": "roots",
"label": "Roots",
"type": "dict-modifiable",
"is_file": true,
"is_group": true,
"expandable": false,
"object_type": {
"type": "path",
"multiplatform": true
}
},
{
"type": "schema",
"name": "schema_anatomy_templates"
},
{
"type": "schema",
"name": "schema_anatomy_attributes"
},
{
"type": "dict-modifiable",
"key": "tasks",
"label": "Task types",
"is_file": true,
"is_group": true,
"object_type": {
"type": "dict",
"children": [
{
"type": "text",
"key": "short_name",
"label": "Short name"
}
]
}
},
{
"type": "schema",
"name": "schema_anatomy_imageio"
}
]
},
{
"type": "dict",
"key": "project_settings",
"children": [
{
"type": "schema",
"name": "schema_project_global"
},
{
"type": "schema",
"name": "schema_project_ftrack"
},
{
"type": "schema",
"name": "schema_project_deadline"
},
{
"type": "schema",
"name": "schema_project_maya"
},
{
"type": "schema",
"name": "schema_project_nuke"
},
{
"type": "schema",
"name": "schema_project_hiero"
},
{
"type": "schema",
"name": "schema_project_harmony"
},
{
"type": "schema",
"name": "schema_project_celaction"
},
{
"type": "schema",
"name": "schema_project_resolve"
},
{
"type": "schema",
"name": "schema_project_standalonepublisher"
},
{
"type": "schema",
"name": "schema_project_unreal"
}
]
}
]
}

View file

@ -0,0 +1,62 @@
{
"type": "dict",
"collapsible": true,
"key": "celaction",
"label": "CelAction",
"is_file": true,
"children": [
{
"type": "dict",
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"children": [
{
"type": "dict",
"collapsible": true,
"checkbox_key": "enabled",
"key": "ExtractCelactionDeadline",
"label": "ExtractCelactionDeadline",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "text",
"key": "deadline_department",
"label": "Deadline apartment"
},
{
"type": "number",
"key": "deadline_priority",
"label": "Deadline priority"
},
{
"type": "text",
"key": "deadline_pool",
"label": "Deadline pool"
},
{
"type": "text",
"key": "deadline_pool_secondary",
"label": "Deadline pool (secondary)"
},
{
"type": "text",
"key": "deadline_group",
"label": "Deadline Group"
},
{
"type": "number",
"key": "deadline_chunk_size",
"label": "Deadline Chunk size"
}
]
}
]
}
]
}

View file

@ -0,0 +1,263 @@
{
"type": "dict",
"key": "deadline",
"label": "Deadline",
"collapsible": true,
"is_file": true,
"children": [
{
"type": "dict",
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"is_file": true,
"children": [
{
"type": "dict",
"collapsible": true,
"key": "MayaSubmitDeadline",
"label": "Submit maya job to deadline",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "boolean",
"key": "optional",
"label": "Optional"
},
{
"type": "boolean",
"key": "active",
"label": "Active"
},
{
"type": "enum",
"key": "tile_assembler_plugin",
"label": "Tile Assembler Plugin",
"multiselection": false,
"enum_items": [
{
"DraftTileAssembler": "Draft Tile Assembler"
},
{
"oiio": "Open Image IO"
}
]
},
{
"type": "boolean",
"key": "use_published",
"label": "Use Published scene"
},
{
"type": "boolean",
"key": "asset_dependencies",
"label": "Use Asset dependencies"
},
{
"type": "text",
"key": "group",
"label": "Group Name"
},
{
"type": "list",
"key": "limit",
"label": "Limit Groups",
"object_type": "text"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "NukeSubmitDeadline",
"label": "Nuke Submit to Deadline",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "boolean",
"key": "optional",
"label": "Optional"
},
{
"type": "boolean",
"key": "active",
"label": "Active"
},
{
"type": "boolean",
"key": "use_published",
"label": "Use Published scene"
},
{
"type": "number",
"key": "priority",
"label": "Priority"
},
{
"type": "number",
"key": "chunk_size",
"label": "Chunk Size"
},
{
"type": "text",
"key": "primary_pool",
"label": "Primary Pool"
},
{
"type": "text",
"key": "secondary_pool",
"label": "Secondary Pool"
},
{
"type": "text",
"key": "group",
"label": "Group"
},
{
"type": "text",
"key": "department",
"label": "Department"
},
{
"type": "dict-modifiable",
"key": "limit_groups",
"label": "Limit Groups",
"object_type": {
"type": "list",
"object_type": "text"
}
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "HarmonySubmitDeadline",
"label": "Harmony Submit to Deadline",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "boolean",
"key": "optional",
"label": "Optional"
},
{
"type": "boolean",
"key": "active",
"label": "Active"
},
{
"type": "boolean",
"key": "use_published",
"label": "Use Published scene"
},
{
"type": "number",
"key": "priority",
"label": "Priority"
},
{
"type": "number",
"key": "chunk_size",
"label": "Chunk Size"
},
{
"type": "text",
"key": "primary_pool",
"label": "Primary Pool"
},
{
"type": "text",
"key": "secondary_pool",
"label": "Secondary Pool"
},
{
"type": "text",
"key": "group",
"label": "Group"
},
{
"type": "text",
"key": "department",
"label": "Department"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "AfterEffectsSubmitDeadline",
"label": "After Effects Submit to Deadline",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "boolean",
"key": "optional",
"label": "Optional"
},
{
"type": "boolean",
"key": "active",
"label": "Active"
},
{
"type": "boolean",
"key": "use_published",
"label": "Use Published scene"
},
{
"type": "number",
"key": "priority",
"label": "Priority"
},
{
"type": "number",
"key": "chunk_size",
"label": "Chunk Size"
},
{
"type": "text",
"key": "primary_pool",
"label": "Primary Pool"
},
{
"type": "text",
"key": "secondary_pool",
"label": "Secondary Pool"
},
{
"type": "text",
"key": "group",
"label": "Group"
},
{
"type": "text",
"key": "department",
"label": "Department"
}
]
}
]
}
]
}

View file

@ -0,0 +1,645 @@
{
"type": "dict",
"key": "ftrack",
"label": "Ftrack",
"collapsible": true,
"is_file": true,
"children": [
{
"type": "dict",
"key": "events",
"label": "Server Actions/Events",
"children": [
{
"type": "dict",
"key": "sync_to_avalon",
"label": "Sync to avalon",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "label",
"label": "Allow name and hierarchy change only if following statuses are on all children tasks"
},
{
"type": "list",
"key": "statuses_name_change",
"label": "Statuses",
"object_type": {
"type": "text",
"multiline": false
}
}
]
},
{
"type": "dict",
"key": "sync_hier_entity_attributes",
"label": "Sync Hierarchical and Entity Attributes",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "interest_entity_types",
"label": "Entity types of interest",
"object_type": {
"type": "text",
"multiline": false
}
},
{
"type": "list",
"key": "interest_attributes",
"label": "Attributes to sync",
"object_type": {
"type": "text",
"multiline": false
}
},
{
"type": "separator"
},
{
"type": "boolean",
"key": "action_enabled",
"label": "Enable Action"
},
{
"type": "list",
"key": "role_list",
"label": "Roles for action",
"object_type": "text"
}
]
},
{
"type": "dict",
"key": "clone_review_session",
"label": "Clone Review Session",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "role_list",
"label": "Roles for action",
"object_type": "text"
}
]
},
{
"type": "dict",
"key": "thumbnail_updates",
"label": "Update Hierarchy thumbnails",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "label",
"label": "Push thumbnail from version, up through multiple hierarchy levels."
},
{
"type": "number",
"key": "levels",
"label": "Levels"
}
]
},
{
"type": "dict",
"key": "user_assignment",
"label": "Run script on user assignments",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
}
]
},
{
"type": "dict",
"key": "status_update",
"label": "Update status on task action",
"is_group": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"key": "mapping",
"type": "dict-modifiable",
"object_type": {
"type": "list",
"object_type": "text"
}
}
]
},
{
"type": "dict",
"key": "status_task_to_parent",
"label": "Sync status from Task to Parent",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "label",
"label": "List of parent object types where this is triggered (\"Shot\", \"Asset Build\", etc.). Skipped if list is empty."
},
{
"type": "list",
"object_type": "text",
"key": "parent_object_types",
"label": "Object types"
},
{
"key": "parent_status_match_all_task_statuses",
"type": "dict-modifiable",
"label": "Change parent if all tasks match",
"object_type": {
"type": "list",
"object_type": "text"
}
},
{
"type": "list",
"key": "parent_status_by_task_status",
"label": "Change parent status if a single task matches",
"use_label_wrap": true,
"object_type": {
"type": "dict",
"children": [
{
"type": "text",
"label": "New parent status",
"key": "new_status"
},
{
"type": "separator"
},
{
"type": "list",
"label": "Task status",
"key": "task_statuses",
"object_type": "text"
}
]
}
}
]
},
{
"type": "dict",
"key": "status_task_to_version",
"label": "Sync status from Task to Version",
"is_group": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "dict-modifiable",
"key": "mapping",
"object_type":
{
"type": "list",
"object_type": "text"
}
},
{
"type": "label",
"label": "<b>Limit<b/> status changes to entered asset types. Limitation is ignored if nothing is entered."
},
{
"type": "list",
"key": "asset_types_filter",
"label": "Asset types (short)",
"object_type": "text"
}
]
},
{
"type": "dict",
"key": "status_version_to_task",
"label": "Sync status from Version to Task",
"is_group": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "label",
"label": "<b>Change Task status based on a changed Version status.</b><br/>Version's new status on the <b>left</b> will trigger a change of a task status to the first available from the list on <b>right</b>.<br/> - if no status from the list is available it will use the same status as the version."
},
{
"type": "dict-modifiable",
"key": "mapping",
"object_type": {
"type": "list",
"object_type": "text"
}
},
{
"type": "separator"
},
{
"type": "label",
"label": "<b>Disable<b/> event if status was changed on specific Asset type."
},
{
"type": "list",
"label": "Asset types (short)",
"key": "asset_types_to_skip",
"object_type": "text"
}
]
},
{
"type": "dict",
"key": "first_version_status",
"label": "Set status on first created version",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "text",
"key": "status",
"label": "Status"
}
]
},
{
"type": "dict",
"key": "next_task_update",
"is_group": true,
"label": "Update status on next task",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "label",
"label": "Change status on next task by <b>task types order</b> when task status state changed to \"Done\". All tasks with same Task type must be \"Done\"."
},
{
"type": "label",
"label": "Mapping of next task status changes <b>From</b> -> <b>To</b>."
},
{
"type": "dict-modifiable",
"key": "mapping",
"object_type": {
"type": "text"
}
},
{
"type": "separator"
},
{
"type": "label",
"label": "Status names that are ignored on \"Done\" check (e.g. \"Omitted\")."
},
{
"type": "list",
"key": "ignored_statuses",
"object_type": "text"
},
{
"type": "separator"
},
{
"type": "label",
"label": "Allow to break rule that all tasks with same Task type must be \"Done\" and change statuses with same type tasks ordered by name."
},
{
"label": "Name sorting",
"type": "boolean",
"key": "name_sorting"
}
]
}
]
},
{
"type": "dict",
"key": "user_handlers",
"label": "User Actions/Events",
"children": [
{
"type": "dict",
"key": "application_launch_statuses",
"is_group": true,
"label": "Application - Status change on launch",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "label",
"label": "<b>Do not change status if current status is:</b>"
},
{
"type": "list",
"key": "ignored_statuses",
"object_type": "text"
},
{
"type": "label",
"label": "Change task's status to <b>left side</b> if current task status is in list on <b>right side</b>."
},
{
"type": "dict-modifiable",
"key": "status_change",
"object_type": {
"type": "list",
"object_type": "text"
}
}
]
},
{
"type": "dict",
"key": "create_update_attributes",
"label": "Create/Update Avalon Attributes",
"children": [
{
"type": "list",
"key": "role_list",
"label": "Roles",
"object_type": "text"
}
]
},
{
"type": "dict",
"key": "prepare_project",
"label": "Prepare Project",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "role_list",
"label": "Roles",
"object_type": "text"
}
]
},
{
"type": "dict",
"key": "clean_hierarchical_attr",
"label": "Clean hierarchical custom attributes",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "role_list",
"label": "Roles",
"object_type": "text"
}
]
},
{
"type": "dict",
"key": "delete_asset_subset",
"label": "Delete Asset/Subsets",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "role_list",
"label": "Roles",
"object_type": "text"
}
]
},
{
"type": "dict",
"key": "delete_old_versions",
"label": "Delete old versions",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "role_list",
"label": "Roles",
"object_type": "text"
}
]
},
{
"type": "dict",
"key": "delivery_action",
"label": "Delivery",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "role_list",
"label": "Roles",
"object_type": "text"
}
]
},
{
"type": "dict",
"key": "store_thubmnail_to_avalon",
"label": "Store Thumbnails to avalon",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "role_list",
"label": "Roles",
"object_type": "text"
}
]
},
{
"type": "dict",
"key": "job_killer",
"label": "Job Killer",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "role_list",
"label": "Roles",
"object_type": "text"
}
]
},
{
"type": "dict",
"key": "sync_to_avalon_local",
"label": "Sync to avalon (local) - For development",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "role_list",
"label": "Roles",
"object_type": "text"
}
]
},
{
"type": "dict",
"key": "seed_project",
"label": "Seed Debug Project",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "role_list",
"label": "Roles",
"object_type": "text"
}
]
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"is_file": true,
"children": [
{
"type": "dict",
"collapsible": true,
"checkbox_key": "enabled",
"key": "IntegrateFtrackNote",
"label": "IntegrateFtrackNote",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "text",
"key": "note_with_intent_template",
"label": "Note with intent template"
},
{
"type": "list",
"object_type": "text",
"key": "note_labels",
"label": "Note labels"
}
]
},
{
"type": "dict",
"collapsible": true,
"checkbox_key": "enabled",
"key": "ValidateFtrackAttributes",
"label": "ValidateFtrackAttributes",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "raw-json",
"key": "ftrack_custom_attributes",
"label": "Custom attributes to validate"
}
]
}
]
}
]
}

View file

@ -0,0 +1,41 @@
{
"type": "dict",
"collapsible": true,
"key": "global",
"label": "Global",
"is_file": true,
"children": [
{
"type": "schema",
"name": "schema_global_publish"
},
{
"type": "schema",
"name": "schema_global_tools"
},
{
"type": "raw-json",
"label": "Project Folder Structure",
"key": "project_folder_structure",
"use_label_wrap": true
},
{
"type": "schema",
"name": "schema_project_syncserver"
},
{
"key": "project_plugins",
"type": "path",
"label": "Additional Project Plugin Paths",
"multiplatform": true,
"multipath": true,
"use_label_wrap": true
},
{
"key": "project_environments",
"type": "raw-json",
"label": "Additional Project Environments (set on application launch)",
"use_label_wrap": true
}
]
}

View file

@ -0,0 +1,84 @@
{
"type": "dict",
"collapsible": true,
"key": "harmony",
"label": "Harmony",
"is_file": true,
"children": [
{
"type": "dict",
"collapsible": true,
"key": "general",
"label": "General",
"children": [
{
"type": "list",
"key": "skip_resolution_check",
"object_type": "text",
"label": "Skip Resolution Check for Tasks"
},
{
"type": "list",
"key": "skip_timelines_check",
"object_type": "text",
"label": "Skip Timeliene Check for Tasks"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"children": [
{
"type": "dict",
"collapsible": true,
"key": "CollectPalettes",
"label": "Collect Palettes",
"children": [
{
"type": "list",
"key": "allowed_tasks",
"label": "Allowed tasks",
"object_type": "text"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "HarmonySubmitDeadline",
"label": "Harmony Submit to Deadline",
"children": [
{
"type": "boolean",
"key": "use_published",
"label": "Use Published scene"
},
{
"type": "number",
"key": "priority",
"label": "priority"
},
{
"type": "text",
"key": "primary_pool",
"label": "Primary Pool"
},
{
"type": "text",
"key": "secondary_pool",
"label": "Secondary Pool"
},
{
"type": "number",
"key": "chunk_size",
"label": "Chunk Size"
}
]
}
]
}
]
}

View file

@ -0,0 +1,172 @@
{
"type": "dict",
"collapsible": true,
"key": "hiero",
"label": "Hiero",
"is_file": true,
"children": [
{
"type": "dict",
"collapsible": true,
"key": "create",
"label": "Create plugins",
"children": [
{
"type": "dict",
"collapsible": true,
"key": "CreateShotClip",
"label": "Create Shot Clip",
"is_group": true,
"children": [
{
"type": "collapsible-wrap",
"label": "Shot Hierarchy And Rename Settings",
"collapsible": false,
"children": [
{
"type": "text",
"key": "hierarchy",
"label": "Shot parent hierarchy"
},
{
"type": "boolean",
"key": "clipRename",
"label": "Rename clips"
},
{
"type": "text",
"key": "clipName",
"label": "Clip name template"
},
{
"type": "number",
"key": "countFrom",
"label": "Count sequence from"
},
{
"type": "number",
"key": "countSteps",
"label": "Stepping number"
}
]
},
{
"type": "collapsible-wrap",
"label": "Shot Template Keywords",
"collapsible": false,
"children": [
{
"type": "text",
"key": "folder",
"label": "{folder}"
},
{
"type": "text",
"key": "episode",
"label": "{episode}"
},
{
"type": "text",
"key": "sequence",
"label": "{sequence}"
},
{
"type": "text",
"key": "track",
"label": "{track}"
},
{
"type": "text",
"key": "shot",
"label": "{shot}"
}
]
},
{
"type": "collapsible-wrap",
"label": "Vertical Synchronization Of Attributes",
"collapsible": false,
"children": [
{
"type": "boolean",
"key": "vSyncOn",
"label": "Enable Vertical Sync"
}
]
},
{
"type": "collapsible-wrap",
"label": "Shot Attributes",
"collapsible": false,
"children": [
{
"type": "number",
"key": "workfileFrameStart",
"label": "Workfiles Start Frame"
},
{
"type": "number",
"key": "handleStart",
"label": "Handle start (head)"
},
{
"type": "number",
"key": "handleEnd",
"label": "Handle end (tail)"
}
]
}
]
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"children": [
{
"type": "dict",
"collapsible": true,
"checkbox_key": "enabled",
"key": "CollectInstanceVersion",
"label": "Collect Instance Version",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
}
]
},
{
"type": "dict",
"collapsible": true,
"checkbox_key": "enabled",
"key": "ExtractReviewCutUpVideo",
"label": "Extract Review Cut Up Video",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"object_type": "text",
"key": "tags_addition",
"label": "Tags addition"
}
]
}
]
},
{
"type": "schema",
"name": "schema_publish_gui_filter"
}
]
}

View file

@ -0,0 +1,38 @@
{
"type": "dict",
"collapsible": true,
"key": "maya",
"label": "Maya",
"is_file": true,
"children": [
{
"type": "dict-modifiable",
"key": "ext_mapping",
"label": "Extension Mapping",
"use_label_wrap": true,
"object_type": {
"type": "text"
}
},
{
"type": "schema",
"name": "schema_maya_create"
},
{
"type": "schema",
"name": "schema_maya_publish"
},
{
"type": "schema",
"name": "schema_maya_load"
},
{
"type": "schema",
"name": "schema_workfile_build"
},
{
"type": "schema",
"name": "schema_publish_gui_filter"
}
]
}

View file

@ -0,0 +1,104 @@
{
"type": "dict",
"collapsible": true,
"key": "nuke",
"label": "Nuke",
"is_file": true,
"children": [
{
"type": "dict",
"collapsible": true,
"key": "general",
"label": "General",
"children": [
{
"type": "dict",
"collapsible": true,
"key": "menu",
"label": "OpenPype Menu shortcuts",
"children": [
{
"type": "text",
"key": "create",
"label": "Create..."
},
{
"type": "text",
"key": "publish",
"label": "Publish..."
},
{
"type": "text",
"key": "load",
"label": "Load..."
},
{
"type": "text",
"key": "manage",
"label": "Manage..."
},
{
"type": "text",
"key": "build_workfile",
"label": "Build Workfile"
}
]
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "create",
"label": "Creator plugins",
"children": [
{
"type": "dict",
"collapsible": false,
"key": "CreateWriteRender",
"label": "CreateWriteRender",
"is_group": true,
"children": [
{
"type": "text",
"key": "fpath_template",
"label": "Path template"
}
]
},
{
"type": "dict",
"collapsible": false,
"key": "CreateWritePrerender",
"label": "CreateWritePrerender",
"is_group": true,
"children": [
{
"type": "text",
"key": "fpath_template",
"label": "Path template"
}
]
}
]
},
{
"type": "schema",
"name": "schema_nuke_publish",
"template_data": []
},
{
"type": "schema",
"name": "schema_nuke_load",
"template_data": []
},
{
"type": "schema",
"name": "schema_workfile_build"
},
{
"type": "schema",
"name": "schema_publish_gui_filter"
}
]
}

View file

@ -0,0 +1,124 @@
{
"type": "dict",
"collapsible": true,
"key": "resolve",
"label": "DaVinci Resolve",
"is_file": true,
"children": [
{
"type": "dict",
"collapsible": true,
"key": "create",
"label": "Creator plugins",
"children": [
{
"type": "dict",
"collapsible": true,
"key": "CreateShotClip",
"label": "Create Shot Clip",
"is_group": true,
"children": [
{
"type": "collapsible-wrap",
"label": "Shot Hierarchy And Rename Settings",
"collapsible": false,
"children": [
{
"type": "text",
"key": "hierarchy",
"label": "Shot parent hierarchy"
},
{
"type": "boolean",
"key": "clipRename",
"label": "Rename clips"
},
{
"type": "text",
"key": "clipName",
"label": "Clip name template"
},
{
"type": "number",
"key": "countFrom",
"label": "Count sequence from"
},
{
"type": "number",
"key": "countSteps",
"label": "Stepping number"
}
]
},
{
"type": "collapsible-wrap",
"label": "Shot Template Keywords",
"collapsible": false,
"children": [
{
"type": "text",
"key": "folder",
"label": "{folder}"
},
{
"type": "text",
"key": "episode",
"label": "{episode}"
},
{
"type": "text",
"key": "sequence",
"label": "{sequence}"
},
{
"type": "text",
"key": "track",
"label": "{track}"
},
{
"type": "text",
"key": "shot",
"label": "{shot}"
}
]
},
{
"type": "collapsible-wrap",
"label": "Vertical Synchronization Of Attributes",
"collapsible": false,
"children": [
{
"type": "boolean",
"key": "vSyncOn",
"label": "Enable Vertical Sync"
}
]
},
{
"type": "collapsible-wrap",
"label": "Shot Attributes",
"collapsible": false,
"children": [
{
"type": "number",
"key": "workfileFrameStart",
"label": "Workfiles Start Frame"
},
{
"type": "number",
"key": "handleStart",
"label": "Handle start (head)"
},
{
"type": "number",
"key": "handleEnd",
"label": "Handle end (tail)"
}
]
}
]
}
]
}
]
}

View file

@ -0,0 +1,93 @@
{
"type": "dict",
"collapsible": true,
"key": "standalonepublisher",
"label": "Standalone Publisher",
"is_file": true,
"children": [
{
"type": "dict-modifiable",
"collapsible": true,
"key": "create",
"label": "Creator plugins",
"collapsible_key": true,
"is_file": true,
"object_type": {
"type": "dict",
"children": [
{
"type": "text",
"key": "name",
"label": "Name"
},
{
"type": "text",
"key": "label",
"label": "Label"
},
{
"type": "text",
"key": "family",
"label": "Family"
},
{
"type": "text",
"key": "icon",
"label": "Icon"
},
{
"type": "list",
"key": "defaults",
"label": "Defaults",
"object_type": {
"type": "text"
}
},
{
"type": "text",
"key": "help",
"label": "Help"
}
]
}
},
{
"type": "dict",
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"is_file": true,
"children": [
{
"type": "dict",
"collapsible": true,
"key": "ExtractThumbnailSP",
"label": "ExtractThumbnailSP",
"is_group": true,
"children": [
{
"type": "dict",
"collapsible": false,
"key": "ffmpeg_args",
"label": "ffmpeg_args",
"children": [
{
"type": "list",
"object_type": "text",
"key": "input",
"label": "input"
},
{
"type": "list",
"object_type": "text",
"key": "output",
"label": "output"
}
]
}
]
}
]
}
]
}

View file

@ -0,0 +1,74 @@
{
"type": "dict",
"key": "sync_server",
"label": "Sync Server (currently unused)",
"collapsible": true,
"checkbox_key": "enabled",
"is_file": true,
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "dict",
"key": "config",
"label": "Config",
"collapsible": true,
"children": [
{
"type": "text",
"key": "retry_cnt",
"label": "Retry Count"
},
{
"type": "text",
"key": "loop_delay",
"label": "Loop Delay"
},
{
"type": "text",
"key": "active_site",
"label": "Active Site"
},
{
"type": "text",
"key": "remote_site",
"label": "Remote Site"
}
]
}, {
"type": "dict-modifiable",
"collapsible": true,
"key": "sites",
"label": "Sites",
"collapsible_key": false,
"is_file": true,
"object_type":
{
"type": "dict",
"children": [
{
"type": "text",
"key": "provider",
"label": "Provider"
},
{
"type": "text",
"key": "credentials_url",
"label": "Credentials url"
},
{
"type": "dict-modifiable",
"key": "root",
"label": "Roots",
"collapsable": false,
"collapsable_key": false,
"object_type": "text"
}
]
}
}
]
}

View file

@ -0,0 +1,27 @@
{
"type": "dict",
"collapsible": true,
"key": "unreal",
"label": "Unreal Engine",
"is_file": true,
"children": [
{
"type": "dict",
"collapsible": true,
"key": "project_setup",
"label": "Project Setup",
"children": [
{
"type": "boolean",
"key": "dev_mode",
"label": "Dev mode"
},
{
"type": "boolean",
"key": "install_unreal_python_engine",
"label": "Install unreal python engine"
}
]
}
]
}

View file

@ -0,0 +1,74 @@
{
"type": "dict",
"collapsible": true,
"key": "attributes",
"label": "Attributes",
"is_file": true,
"is_group": true,
"children": [
{
"type": "number",
"key": "fps",
"label": "Frame Rate",
"decimal": 2,
"minimum": 0
},
{
"type": "number",
"key": "frameStart",
"label": "Frame Start"
},
{
"type": "number",
"key": "frameEnd",
"label": "Frame End"
},
{
"type": "number",
"key": "clipIn",
"label": "Clip In"
},
{
"type": "number",
"key": "clipOut",
"label": "Clip Out"
},
{
"type": "number",
"key": "handleStart",
"label": "Handle Start"
},
{
"type": "number",
"key": "handleEnd",
"label": "Handle End"
},
{
"type": "number",
"key": "resolutionWidth",
"label": "Resolution Width"
},
{
"type": "number",
"key": "resolutionHeight",
"label": "Resolution Height"
},
{
"type": "number",
"key": "pixelAspect",
"label": "Pixel Aspect Ratio",
"decimal": 2,
"minimum": 0
},
{
"type": "apps-enum",
"key": "applications",
"label": "Applications"
},
{
"type": "tools-enum",
"key": "tools_env",
"label": "Tools"
}
]
}

View file

@ -0,0 +1,352 @@
{
"type": "dict",
"key": "imageio",
"label": "Color Management and Output Formats",
"is_file": true,
"is_group": true,
"children": [
{
"key": "hiero",
"type": "dict",
"label": "Hiero",
"children": [
{
"key": "workfile",
"type": "dict",
"label": "Workfile",
"collapsible": false,
"children": [
{
"type": "form",
"children": [
{
"type": "enum",
"key": "ocioConfigName",
"label": "OpenColorIO Config",
"enum_items": [
{
"nuke-default": "nuke-default"
},
{
"aces_1.0.3": "aces_1.0.3"
},
{
"aces_1.1": "aces_1.1"
},
{
"custom": "custom"
}
]
},
{
"type": "path",
"key": "ocioconfigpath",
"label": "Custom OCIO path",
"multiplatform": true,
"multipath": true
},
{
"type": "text",
"key": "workingSpace",
"label": "Working Space"
},
{
"type": "text",
"key": "sixteenBitLut",
"label": "16 Bit Files"
},
{
"type": "text",
"key": "eightBitLut",
"label": "8 Bit Files"
},
{
"type": "text",
"key": "floatLut",
"label": "Floating Point Files"
},
{
"type": "text",
"key": "logLut",
"label": "Log Files"
},
{
"type": "text",
"key": "viewerLut",
"label": "Viewer"
},
{
"type": "text",
"key": "thumbnailLut",
"label": "Thumbnails"
}
]
}
]
},
{
"key": "regexInputs",
"type": "dict",
"label": "Colorspace on Inputs by regex detection",
"collapsible": true,
"children": [
{
"type": "list",
"key": "inputs",
"object_type": {
"type": "dict",
"children": [
{
"type": "text",
"key": "regex",
"label": "Regex"
},
{
"type": "text",
"key": "colorspace",
"label": "Colorspace"
}
]
}
}
]
}
]
},
{
"key": "nuke",
"type": "dict",
"label": "Nuke",
"children": [
{
"key": "workfile",
"type": "dict",
"label": "Workfile",
"collapsible": false,
"children": [
{
"type": "form",
"children": [
{
"type": "enum",
"key": "colorManagement",
"label": "color management",
"enum_items": [
{
"Nuke": "Nuke"
},
{
"OCIO": "OCIO"
}
]
},
{
"type": "enum",
"key": "OCIO_config",
"label": "OpenColorIO Config",
"enum_items": [
{
"nuke-default": "nuke-default"
},
{
"spi-vfx": "spi-vfx"
},
{
"spi-anim": "spi-anim"
},
{
"aces_0.1.1": "aces_0.1.1"
},
{
"aces_0.7.1": "aces_0.7.1"
},
{
"aces_1.0.1": "aces_1.0.1"
},
{
"aces_1.0.3": "aces_1.0.3"
},
{
"aces_1.1": "aces_1.1"
},
{
"custom": "custom"
}
]
},
{
"type": "path",
"key": "customOCIOConfigPath",
"label": "Custom OCIO config path",
"multiplatform": true,
"multipath": true
},
{
"type": "text",
"key": "workingSpaceLUT",
"label": "Working Space"
},
{
"type": "text",
"key": "monitorLut",
"label": "monitor"
},
{
"type": "text",
"key": "int8Lut",
"label": "8-bit files"
},
{
"type": "text",
"key": "int16Lut",
"label": "16-bit files"
},
{
"type": "text",
"key": "logLut",
"label": "log files"
},
{
"type": "text",
"key": "floatLut",
"label": "float files"
}
]
}
]
},
{
"key": "nodes",
"type": "dict",
"label": "Nodes",
"collapsible": true,
"children": [
{
"key": "requiredNodes",
"type": "list",
"label": "Required Nodes",
"object_type": {
"type": "dict",
"children": [
{
"type": "list",
"key": "plugins",
"label": "Used in plugins",
"object_type": {
"type": "text",
"key": "pluginClass",
"label": "Plugin Class"
}
},
{
"type": "text",
"key": "nukeNodeClass",
"label": "Nuke Node Class"
},
{
"type": "splitter"
},
{
"key": "knobs",
"label": "Knobs",
"type": "list",
"object_type": {
"type": "dict",
"children": [
{
"type": "text",
"key": "name",
"label": "Name"
},
{
"type": "text",
"key": "value",
"label": "Value"
}
]
}
}
]
}
},
{
"type": "list",
"key": "customNodes",
"label": "Custom Nodes",
"object_type": {
"type": "dict",
"children": [
{
"type": "list",
"key": "plugins",
"label": "Used in plugins",
"object_type": {
"type": "text",
"key": "pluginClass",
"label": "Plugin Class"
}
},
{
"type": "text",
"key": "nukeNodeClass",
"label": "Nuke Node Class"
},
{
"type": "splitter"
},
{
"key": "knobs",
"label": "Knobs",
"type": "list",
"object_type": {
"type": "dict",
"children": [
{
"type": "text",
"key": "name",
"label": "Name"
},
{
"type": "text",
"key": "value",
"label": "Value"
}
]
}
}
]
}
}
]
},
{
"key": "regexInputs",
"type": "dict",
"label": "Colorspace on Inputs by regex detection",
"collapsible": true,
"children": [
{
"type": "list",
"key": "inputs",
"object_type": {
"type": "dict",
"children": [
{
"type": "text",
"key": "regex",
"label": "Regex"
},
{
"type": "text",
"key": "colorspace",
"label": "Colorspace"
}
]
}
}
]
}
]
}
]
}

View file

@ -0,0 +1,153 @@
{
"type": "dict",
"key": "templates",
"label": "Templates",
"collapsible": true,
"collapsible_key": true,
"is_file": true,
"is_group": true,
"children": [
{
"type": "dict",
"key": "defaults",
"children": [
{
"type": "number",
"key": "version_padding",
"label": "Version Padding",
"minimum": 1,
"maximum": 10
},
{
"type": "text",
"key": "version",
"label": "Version"
},
{
"type": "number",
"key": "frame_padding",
"label": "Frame Padding",
"minimum": 1,
"maximum": 10
},
{
"type": "text",
"key": "frame",
"label": "Frame"
}
]
},
{
"type": "separator"
},
{
"type": "dict",
"key": "work",
"label": "Work",
"children": [
{
"type": "text",
"key": "folder",
"label": "Folder"
},
{
"type": "text",
"key": "file",
"label": "File"
},
{
"type": "text",
"key": "path",
"label": "Path"
}
]
},
{
"type": "dict",
"key": "render",
"label": "Render",
"children": [
{
"type": "text",
"key": "folder",
"label": "Folder"
},
{
"type": "text",
"key": "file",
"label": "File"
},
{
"type": "text",
"key": "path",
"label": "Path"
}
]
},
{
"type": "dict",
"key": "publish",
"label": "Publish",
"children": [
{
"type": "text",
"key": "folder",
"label": "Folder"
},
{
"type": "text",
"key": "file",
"label": "File"
},
{
"type": "text",
"key": "path",
"label": "Path"
},
{
"type": "text",
"key": "thumbnail",
"label": "Thumbnail"
}
]
},
{
"type": "dict",
"key": "hero",
"label": "Hero",
"children": [
{
"type": "text",
"key": "folder",
"label": "Folder"
},
{
"type": "text",
"key": "file",
"label": "File"
},
{
"type": "text",
"key": "path",
"label": "Path"
}
]
},
{
"type": "dict-modifiable",
"key": "delivery",
"label": "Delivery",
"object_type": "text"
},
{
"type": "dict-modifiable",
"key": "others",
"label": "Others",
"collapsible_key": true,
"object_type": {
"type": "dict-modifiable",
"object_type": "text"
}
}
]
}

View file

@ -0,0 +1,439 @@
{
"type": "dict",
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"children": [
{
"type": "dict",
"collapsible": true,
"checkbox_key": "enabled",
"key": "IntegrateHeroVersion",
"label": "IntegrateHeroVersion",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
}
]
},
{
"type": "dict",
"collapsible": true,
"checkbox_key": "enabled",
"key": "ExtractJpegEXR",
"label": "ExtractJpegEXR",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "dict",
"key": "ffmpeg_args",
"children": [
{
"type": "list",
"object_type": "text",
"key": "input",
"label": "FFmpeg input arguments"
},
{
"type": "list",
"object_type": "text",
"key": "output",
"label": "FFmpeg output arguments"
}
]
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "ExtractReview",
"label": "ExtractReview",
"checkbox_key": "enabled",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "profiles",
"label": "Profiles",
"object_type": {
"type": "dict",
"children": [
{
"key": "families",
"label": "Families",
"type": "list",
"object_type": "text"
},
{
"key": "hosts",
"label": "Hosts",
"type": "list",
"object_type": "text"
},
{
"type": "splitter"
},
{
"key": "outputs",
"label": "Output Definitions",
"type": "dict-modifiable",
"highlight_content": true,
"object_type": {
"type": "dict",
"children": [
{
"key": "ext",
"label": "Output extension",
"type": "text"
},
{
"key": "tags",
"label": "Tags",
"type": "enum",
"multiselection": true,
"enum_items": [
{
"burnin": "Add burnins"
},
{
"ftrackreview": "Add to Ftrack"
},
{
"delete": "Delete output"
},
{
"slate-frame": "Add slate frame"
},
{
"no-handles": "Skip handle frames"
},
{
"sequence": "Output as image sequence"
}
]
},
{
"key": "ffmpeg_args",
"label": "FFmpeg arguments",
"type": "dict",
"highlight_content": true,
"children": [
{
"key": "video_filters",
"label": "Video filters",
"type": "list",
"object_type": "text"
},
{
"type": "splitter"
},
{
"key": "audio_filters",
"label": "Audio filters",
"type": "list",
"object_type": "text"
},
{
"type": "splitter"
},
{
"key": "input",
"label": "Input arguments",
"type": "list",
"object_type": "text"
},
{
"type": "splitter"
},
{
"key": "output",
"label": "Output arguments",
"type": "list",
"object_type": "text"
}
]
},
{
"key": "filter",
"label": "Additional output filtering",
"type": "dict",
"highlight_content": true,
"children": [
{
"key": "families",
"label": "Families",
"type": "list",
"object_type": "text"
}
]
},
{
"type": "separator"
},
{
"type": "label",
"label": "Width and Height must be both set to higher value than 0 else source resolution is used."
},
{
"key": "width",
"label": "Output width",
"type": "number",
"default": 0,
"minimum": 0,
"maximum": 100000
},
{
"key": "height",
"label": "Output height",
"type": "number",
"default": 0,
"minimum": 0,
"maximum": 100000
}
]
}
}
]
}
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "ExtractBurnin",
"label": "ExtractBurnin",
"checkbox_key": "enabled",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "dict",
"collapsible": true,
"key": "options",
"label": "Burnin formating options",
"children": [
{
"type": "number",
"key": "font_size",
"label": "Font size",
"minimum": 0
},
{
"type": "number",
"key": "opacity",
"label": "Font opacity",
"decimal": 2,
"maximum": 1,
"minimum": 0
},
{
"type": "number",
"key": "bg_opacity",
"label": "Background opacity",
"decimal": 2,
"maximum": 1,
"minimum": 0
},
{
"type": "number",
"key": "x_offset",
"label": "X Offset"
},
{
"type": "number",
"key": "y_offset",
"label": "Y Offset"
},
{
"type": "number",
"key": "bg_padding",
"label": "Padding aroung text"
}
]
},
{
"type": "separator"
},
{
"type": "list",
"key": "profiles",
"label": "Profiles",
"object_type": {
"type": "dict",
"children": [
{
"key": "families",
"label": "Families",
"type": "list",
"object_type": "text"
},
{
"key": "hosts",
"label": "Hosts",
"type": "list",
"object_type": "text"
},
{
"type": "splitter"
},
{
"key": "burnins",
"label": "Burnins",
"type": "dict-modifiable",
"highlight_content": true,
"collapsible": false,
"object_type": {
"type": "dict",
"children": [
{
"key": "TOP_LEFT",
"label": "Top Left",
"type": "text"
},
{
"key": "TOP_CENTERED",
"label": "Top Centered",
"type": "text"
},
{
"key": "TOP_RIGHT",
"label": "top Right",
"type": "text"
},
{
"key": "BOTTOM_LEFT",
"label": "Bottom Left",
"type": "text"
},
{
"key": "BOTTOM_CENTERED",
"label": "Bottom Centered",
"type": "text"
},
{
"key": "BOTTOM_RIGHT",
"label": "BottomRight",
"type": "text"
}
]
}
}
]
}
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "IntegrateAssetNew",
"label": "IntegrateAssetNew",
"is_group": true,
"children": [
{
"type": "raw-json",
"key": "template_name_profiles",
"label": "template_name_profiles"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "ProcessSubmittedJobOnFarm",
"label": "ProcessSubmittedJobOnFarm",
"checkbox_key": "enabled",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "text",
"key": "deadline_department",
"label": "Deadline department"
},
{
"type": "text",
"key": "deadline_pool",
"label": "Deadline Pool"
},
{
"type": "text",
"key": "deadline_group",
"label": "Deadline Group"
},
{
"type": "number",
"key": "deadline_chunk_size",
"label": "Deadline Chunk Size"
},
{
"type": "number",
"key": "deadline_priority",
"label": "Deadline Priotity"
},
{
"type": "dict",
"key": "aov_filter",
"label": "Reviewable subsets filter",
"children": [
{
"type": "list",
"key": "maya",
"label": "Maya",
"object_type": {
"type": "text"
}
},
{
"type": "list",
"key": "nuke",
"label": "Nuke",
"object_type": {
"type": "text"
}
},
{
"type": "list",
"key": "aftereffects",
"label": "After Effects",
"object_type": {
"type": "text"
}
},
{
"type": "list",
"key": "celaction",
"label": "Celaction",
"object_type": {
"type": "text"
}
}
]
}
]
}
]
}

View file

@ -0,0 +1,114 @@
{
"type": "dict",
"collapsible": true,
"key": "tools",
"label": "Tools",
"children": [
{
"type": "dict",
"collapsible": true,
"key": "creator",
"label": "Creator",
"children": [
{
"type": "dict-modifiable",
"collapsible": true,
"key": "families_smart_select",
"label": "Families smart select",
"object_type": {
"type": "list",
"object_type": "text"
}
},
{
"type": "list",
"key": "subset_name_profiles",
"label": "Subset name profiles",
"use_label_wrap": true,
"object_type": {
"type": "dict",
"children": [
{
"key": "families",
"label": "Families",
"type": "list",
"object_type": "text"
},
{
"key": "hosts",
"label": "Hosts",
"type": "list",
"object_type": "text"
},
{
"key": "tasks",
"label": "Task names",
"type": "list",
"object_type": "text"
},
{
"type": "separator"
},
{
"type": "text",
"key": "template",
"label": "Template"
}
]
}
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "Workfiles",
"label": "Workfiles",
"children": [
{
"type": "list",
"key": "last_workfile_on_startup",
"label": "Open last workfiles on launch",
"is_group": true,
"use_label_wrap": true,
"object_type": {
"type": "dict",
"children": [
{
"key": "hosts",
"label": "Hosts",
"type": "list",
"object_type": "text"
},
{
"key": "tasks",
"label": "Tasks",
"type": "list",
"object_type": "text"
},
{
"type": "splitter"
},
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
}
]
}
},
{
"type": "dict-modifiable",
"collapsible": true,
"key": "sw_folders",
"label": "Extra task folders",
"is_group": true,
"object_type": {
"type": "list",
"object_type": "text"
}
}
]
}
]
}

View file

@ -0,0 +1,86 @@
{
"type": "dict",
"collapsible": true,
"key": "create",
"label": "Creator plugins",
"children": [
{
"type": "schema_template",
"name": "template_create_plugin",
"template_data": [
{
"key": "CreateAnimation",
"label": "Create Animation"
},
{
"key": "CreateAss",
"label": "Create Ass"
},
{
"key": "CreateAssembly",
"label": "Create Assembly"
},
{
"key": "CreateCamera",
"label": "Create Camera"
},
{
"key": "CreateLayout",
"label": "Create Layout"
},
{
"key": "CreateLook",
"label": "Create Look"
},
{
"key": "CreateMayaScene",
"label": "Create Maya Scene"
},
{
"key": "CreateModel",
"label": "Create Model"
},
{
"key": "CreatePointCache",
"label": "Create Cache"
},
{
"key": "CreateRender",
"label": "Create Render"
},
{
"key": "CreateRenderSetup",
"label": "Create Render Setup"
},
{
"key": "CreateReview",
"label": "Create Review"
},
{
"key": "CreateRig",
"label": "Create Rig"
},
{
"key": "CreateSetDress",
"label": "Create Set Dress"
},
{
"key": "CreateUnrealStaticMesh",
"label": "Create Unreal - Static Mesh"
},
{
"key": "CreateVrayProxy",
"label": "Create VRay Proxy"
},
{
"key": "CreateVRayScene",
"label": "Create VRay Scene"
},
{
"key": "CreateYetiRig",
"label": "Create Yeti Rig"
}
]
}
]
}

View file

@ -0,0 +1,156 @@
{
"type": "dict",
"collapsible": true,
"key": "load",
"label": "Loader plugins",
"children": [
{
"type": "dict",
"collapsible": true,
"key": "colors",
"label": "Loaded Subsets Outliner Colors",
"children": [
{
"type": "schema_template",
"name": "template_color",
"template_data": [
{
"label": "Model",
"name": "model"
}
]
},
{
"type": "schema_template",
"name": "template_color",
"template_data": [
{
"label": "Rig",
"name": "rig"
}
]
},
{
"type": "schema_template",
"name": "template_color",
"template_data": [
{
"label": "Pointcache",
"name": "pointcache"
}
]
},
{
"type": "schema_template",
"name": "template_color",
"template_data": [
{
"label": "Animation",
"name": "animation"
}
]
},
{
"type": "schema_template",
"name": "template_color",
"template_data": [
{
"label": "Arnold Standin",
"name": "ass"
}
]
},
{
"type": "schema_template",
"name": "template_color",
"template_data": [
{
"label": "Camera",
"name": "camera"
}
]
},
{
"type": "schema_template",
"name": "template_color",
"template_data": [
{
"label": "FBX",
"name": "fbx"
}
]
},
{
"type": "schema_template",
"name": "template_color",
"template_data": [
{
"label": "Maya Scene",
"name": "mayaAscii"
}
]
},
{
"type": "schema_template",
"name": "template_color",
"template_data": [
{
"label": "Set Dress",
"name": "setdress"
}
]
},
{
"type": "schema_template",
"name": "template_color",
"template_data": [
{
"label": "Layout",
"name": "layout"
}
]
},
{
"type": "schema_template",
"name": "template_color",
"template_data": [
{
"label": "VDB Cache",
"name": "vdbcache"
}
]
},
{
"type": "schema_template",
"name": "template_color",
"template_data": [
{
"label": "Vray Proxy",
"name": "vrayproxy"
}
]
},
{
"type": "schema_template",
"name": "template_color",
"template_data": [
{
"label": "Yeti Cache",
"name": "yeticache"
}
]
},
{
"type": "schema_template",
"name": "template_color",
"template_data": [
{
"label": "Yeti Rig",
"name": "yetiRig"
}
]
}
]
}
]
}

View file

@ -0,0 +1,316 @@
{
"type": "dict",
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"children": [
{
"type": "label",
"label": "Collectors"
},
{
"type": "dict",
"collapsible": true,
"key": "CollectMayaRender",
"label": "Collect Render Layers",
"children": [
{
"type": "boolean",
"key": "sync_workfile_version",
"label": "Sync render version with workfile"
}
]
},
{
"type": "splitter"
},
{
"type": "label",
"label": "Validators"
},
{
"type": "dict",
"collapsible": true,
"key": "ValidateShaderName",
"label": "ValidateShaderName",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "label",
"label": "Shader name regex can use named capture group <b>asset</b> to validate against current asset name.<p><b>Example:</b><br/><code>^.*(?P=&lt;asset&gt;.+)_SHD</code></p>"
},
{
"type": "text",
"key": "regex",
"label": "Validation regex"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "ValidateAttributes",
"label": "ValidateAttributes",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "raw-json",
"key": "attributes",
"label": "Attributes"
}
]
},
{
"type": "collapsible-wrap",
"label": "Model",
"children": [
{
"type": "dict",
"collapsible": true,
"key": "ValidateModelName",
"label": "Validate Model Name",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "label",
"label": "Path to material file defining list of material names to check. This is material name per line simple text file.<br/>It will be checked against named group <b>shader</b> in your <em>Validation regex</em>.<p>For example: <br/> <code>^.*(?P=&lt;shader&gt;.+)_GEO</code></p>"
},
{
"type": "path",
"key": "material_file",
"label": "Material File",
"multiplatform": true,
"multipath": false
},
{
"type": "text",
"key": "regex",
"label": "Validation regex"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "ValidateTransformNamingSuffix",
"label": "ValidateTransformNamingSuffix",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "label",
"label": "Validates transform suffix based on the type of its children shapes."
},
{
"type": "raw-json",
"key": "SUFFIX_NAMING_TABLE",
"label": "Suffix Naming Table"
},
{
"type": "boolean",
"key": "ALLOW_IF_NOT_IN_SUFFIX_TABLE",
"label": "Allow if suffix not in table"
}
]
},
{
"type": "schema_template",
"name": "template_publish_plugin",
"template_data": [
{
"key": "ValidateColorSets",
"label": "ValidateColorSets"
},
{
"key": "ValidateMeshHasOverlappingUVs",
"label": "ValidateMeshHasOverlappingUVs"
},
{
"key": "ValidateMeshArnoldAttributes",
"label": "ValidateMeshArnoldAttributes"
},
{
"key": "ValidateMeshShaderConnections",
"label": "ValidateMeshShaderConnections"
},
{
"key": "ValidateMeshSingleUVSet",
"label": "ValidateMeshSingleUVSet"
},
{
"key": "ValidateMeshHasUVs",
"label": "ValidateMeshHasUVs"
},
{
"key": "ValidateMeshLaminaFaces",
"label": "ValidateMeshLaminaFaces"
},
{
"key": "ValidateMeshNonManifold",
"label": "ValidateMeshNonManifold"
},
{
"key": "ValidateMeshNormalsUnlocked",
"label": "ValidateMeshNormalsUnlocked"
},
{
"key": "ValidateMeshUVSetMap1",
"label": "ValidateMeshUVSetMap1",
"docstring": "Validate model's default uv set exists and is named 'map1'.<br><br>In Maya meshes by default have a uv set named 'map1' that cannot be deleted. It can be renamed, however,<br> introducing some issues with some renderers. As such we ensure the first (default) UV set index is named 'map1'."
},
{
"key": "ValidateMeshVerticesHaveEdges",
"label": "ValidateMeshVerticesHaveEdges"
},
{
"key": "ValidateNoAnimation",
"label": "ValidateNoAnimation",
"docstring": "Ensure no keyframes on nodes in the Instance.<br>Even though a Model would extract without animCurves correctly this avoids getting different <br> output from a model when extracted from a different frame than the first frame. (Might be overly restrictive though)."
},
{
"key": "ValidateNoNamespace",
"label": "ValidateNoNamespace"
},
{
"key": "ValidateNoNullTransforms",
"label": "ValidateNoNullTransforms"
},
{
"key": "ValidateNoUnknownNodes",
"label": "ValidateNoUnknownNodes"
},
{
"key": "ValidateNodeNoGhosting",
"label": "ValidateNodeNoGhosting"
},
{
"key": "ValidateShapeDefaultNames",
"label": "ValidateShapeDefaultNames"
},
{
"key": "ValidateShapeRenderStats",
"label": "ValidateShapeRenderStats"
},
{
"key": "ValidateTransformZero",
"label": "ValidateTransformZero"
}
]
}
]
},
{
"type": "schema_template",
"name": "template_publish_plugin",
"template_data": [
{
"key": "ValidateCameraAttributes",
"label": "Validate Camera Attributes",
"docstring": ""
},
{
"key": "ValidateAssemblyName",
"label": "Validate Assembly Name"
},
{
"key": "ValidateAssRelativePaths",
"label": "ValidateAssRelativePaths"
}
]
},
{
"type": "splitter"
},
{
"type": "label",
"label": "Extractors"
},
{
"type": "schema_template",
"name": "template_maya_capture"
},
{
"type": "dict",
"collapsible": true,
"key": "ExtractCameraAlembic",
"label": "Extract camera to Alembic",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "label",
"label": "List of attributes that will be added to the baked alembic camera. Needs to be written in python list syntax.<p>For example: <br/> <code>[\"attributeName\", \"anotherAttribute\"]</code></p>"
},
{
"type": "boolean",
"key": "optional",
"label": "Optional"
},
{
"type": "boolean",
"key": "active",
"label": "Active"
},
{
"type": "raw-json",
"key": "bake_attributes",
"label": "Bake Attributes",
"is_list": true
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "MayaSubmitDeadline",
"label": "Submit maya job to deadline",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "enum",
"key": "tile_assembler_plugin",
"label": "Tile Assembler Plugin",
"multiselection": false,
"enum_items": [
{
"DraftTileAssembler": "Draft Tile Assembler"
},
{
"oiio": "Open Image IO"
}
]
}
]
}
]
}

View file

@ -0,0 +1,26 @@
{
"type": "dict",
"collapsible": true,
"key": "load",
"label": "Loader plugins",
"children": [
{
"type": "schema_template",
"name": "template_loader_plugin_nuke",
"template_data": [
{
"key": "LoadImage",
"label": "Image Loader"
},
{
"key": "LoadMov",
"label": "Movie Loader"
},
{
"key": "LoadSequence",
"label": "Image Sequence Loader"
}
]
}
]
}

View file

@ -0,0 +1,180 @@
{
"type": "dict",
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"children": [
{
"type": "label",
"label": "Collectors"
},
{
"type": "dict",
"collapsible": true,
"key": "PreCollectNukeInstances",
"label": "PreCollectNukeInstances",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "sync_workfile_version",
"label": "Sync Version from workfile"
}
]
},
{
"type": "splitter"
},
{
"type": "label",
"label": "Validators"
},
{
"type": "dict",
"collapsible": true,
"checkbox_key": "enabled",
"key": "ValidateKnobs",
"label": "ValidateKnobs",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "raw-json",
"key": "knobs",
"label": "Knobs"
}
]
},
{
"type": "schema_template",
"name": "template_publish_plugin",
"template_data": [
{
"key": "ValidateOutputResolution",
"label": "Validate Output Resolution"
},
{
"key": "ValidateGizmo",
"label": "Validate Gizmo (Group)"
},
{
"key": "ValidateScript",
"label": "Validate script settings"
},
{
"key": "ValidateNukeWriteBoundingBox",
"label": "Validate and Write Bounding Box"
}
]
},
{
"type": "splitter"
},
{
"type": "label",
"label": "Extractors"
},
{
"type": "dict",
"collapsible": true,
"checkbox_key": "enabled",
"key": "ExtractThumbnail",
"label": "ExtractThumbnail",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "raw-json",
"key": "nodes",
"label": "Nodes"
}
]
},
{
"type": "dict",
"collapsible": true,
"checkbox_key": "enabled",
"key": "ExtractReviewDataLut",
"label": "ExtractReviewDataLut",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
}
]
},
{
"type": "dict",
"collapsible": true,
"checkbox_key": "enabled",
"key": "ExtractReviewDataMov",
"label": "ExtractReviewDataMov",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "boolean",
"key": "viewer_lut_raw",
"label": "Viewer LUT raw"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "ExtractSlateFrame",
"label": "ExtractSlateFrame",
"is_group": true,
"children": [
{
"type": "boolean",
"key": "viewer_lut_raw",
"label": "Viewer LUT raw"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "NukeSubmitDeadline",
"label": "NukeSubmitDeadline",
"is_group": true,
"children": [
{
"type": "number",
"key": "deadline_priority",
"label": "deadline_priority"
},
{
"type": "text",
"key": "deadline_pool",
"label": "deadline_pool"
},
{
"type": "text",
"key": "deadline_pool_secondary",
"label": "deadline_pool_secondary"
},
{
"type": "number",
"key": "deadline_chunk_size",
"label": "deadline_chunk_size"
}
]
}
]
}

View file

@ -0,0 +1,10 @@
{
"type": "dict-modifiable",
"collapsible": true,
"key": "filters",
"label": "Publish GUI Filters",
"object_type": {
"type": "raw-json",
"label": "Plugins"
}
}

View file

@ -0,0 +1,97 @@
{
"type": "dict",
"collapsible": true,
"key": "workfile_build",
"label": "Workfile Build Settings",
"children": [
{
"type": "list",
"key": "profiles",
"label": "Profiles",
"object_type": {
"type": "dict",
"children": [
{
"key": "tasks",
"label": "Tasks",
"type": "list",
"object_type": "text"
},
{
"type": "splitter"
},
{
"key": "current_context",
"label": "<b>Current Context</b>",
"type": "list",
"highlight_content": true,
"object_type": {
"type": "dict",
"children": [
{
"key": "subset_name_filters",
"label": "Subset name Filters",
"type": "list",
"object_type": "text"
},
{
"key": "families",
"label": "Families",
"type": "list",
"object_type": "text"
},
{
"key": "repre_names",
"label": "Repre Names",
"type": "list",
"object_type": "text"
},
{
"key": "loaders",
"label": "Loaders",
"type": "list",
"object_type": "text"
}
]
}
},
{
"key": "linked_assets",
"label": "<b>Linked Assets</b>",
"type": "list",
"highlight_content": true,
"object_type": {
"type": "dict",
"children": [
{
"key": "subset_name_filters",
"label": "Subset name Filters",
"type": "list",
"object_type": "text"
},
{
"key": "families",
"label": "Families",
"type": "list",
"object_type": "text"
},
{
"key": "repre_names",
"label": "Repre Names",
"type": "list",
"object_type": "text"
},
{
"key": "loaders",
"label": "Loaders",
"type": "list",
"object_type": "text"
}
]
}
}
]
}
}
]
}

View file

@ -0,0 +1,30 @@
[
{
"type": "list-strict",
"key": "{name}",
"label": "{label}:",
"object_types": [
{
"label": "Red",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Green",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Blue",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
}
]
}
]

View file

@ -0,0 +1,22 @@
[
{
"type": "dict",
"collapsible": true,
"key": "{key}",
"label": "{label}",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "defaults",
"label": "Default Subsets",
"object_type": "text"
}
]
}
]

View file

@ -0,0 +1,33 @@
[
{
"type": "dict",
"collapsible": true,
"key": "{key}",
"label": "{label}",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "list",
"key": "families",
"label": "Families",
"object_type": "text"
},
{
"type": "list",
"key": "representations",
"label": "Representations",
"object_type": "text"
},
{
"type": "text",
"key": "node_name_template",
"label": "Node name template"
}
]
}
]

View file

@ -0,0 +1,541 @@
[
{
"type": "dict",
"collapsible": true,
"key": "ExtractPlayblast",
"label": "Extract Playblast settings",
"children": [
{
"type": "dict",
"key": "capture_preset",
"children": [
{
"type": "dict",
"key": "Codec",
"children": [
{
"type": "label",
"label": "<b>Codec</b>"
},
{
"type": "text",
"key": "compression",
"label": "Compression type"
},
{
"type": "text",
"key": "format",
"label": "Data format"
},
{
"type": "number",
"key": "quality",
"label": "Quality",
"decimal": 0,
"minimum": 0,
"maximum": 100
},
{
"type": "splitter"
}
]
},
{
"type": "dict",
"key": "Display Options",
"children": [
{
"type": "label",
"label": "<b>Display Options</b>"
},
{
"type": "list-strict",
"key": "background",
"label": "Background Color: ",
"object_types": [
{
"label": "Red",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Green",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Blue",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
}
]
},
{
"type": "list-strict",
"key": "backgroundBottom",
"label": "Background Bottom: ",
"object_types": [
{
"label": "Red",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Green",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Blue",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
}
]
},
{
"type": "list-strict",
"key": "backgroundTop",
"label": "Background Top: ",
"object_types": [
{
"label": "Red",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Green",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
},
{
"label": "Blue",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 3
}
]
},
{
"type": "boolean",
"key": "override_display",
"label": "Override display options"
}
]
},
{
"type": "splitter"
},
{
"type": "dict",
"key": "Generic",
"children": [
{
"type": "label",
"label": "<b>Generic</b>"
},
{
"type": "boolean",
"key": "isolate_view",
"label": " Isolate view"
},
{
"type": "boolean",
"key": "off_screen",
"label": " Off Screen"
}
]
},
{
"type": "dict",
"key": "PanZoom",
"children": [
{
"type": "boolean",
"key": "pan_zoom",
"label": " Pan Zoom"
}
]
},
{
"type": "splitter"
},
{
"type": "dict",
"key": "Renderer",
"children": [
{
"type": "label",
"label": "<b>Renderer</b>"
},
{
"type": "enum",
"key": "rendererName",
"label": "Renderer name",
"enum_items": [
{ "vp2Renderer": "Viewport 2.0" }
]
}
]
},
{
"type": "dict",
"key": "Resolution",
"children": [
{
"type": "splitter"
},
{
"type": "label",
"label": "<b>Resolution</b>"
},
{
"type": "number",
"key": "width",
"label": " Width",
"decimal": 0,
"minimum": 0,
"maximum": 99999
},
{
"type": "number",
"key": "height",
"label": "Height",
"decimal": 0,
"minimum": 0,
"maximum": 99999
},
{
"type": "number",
"key": "percent",
"label": "percent",
"decimal": 1,
"minimum": 0,
"maximum": 200
},
{
"type": "text",
"key": "mode",
"label": "Mode"
}
]
},
{
"type": "splitter"
},
{
"type": "dict",
"collapsible": true,
"key": "Viewport Options",
"label": "Viewport Options",
"children": [
{
"type": "boolean",
"key": "override_viewport_options",
"label": "override_viewport_options"
},
{
"type": "enum",
"key": "displayLights",
"label": "Display Lights",
"enum_items": [
{ "default": "Default Lighting"},
{ "all": "All Lights"},
{ "selected": "Selected Lights"},
{ "flat": "Flat Lighting"},
{ "nolights": "No Lights"}
]
},
{
"type": "number",
"key": "textureMaxResolution",
"label": "Texture Clamp Resolution",
"decimal": 0
},
{
"type": "number",
"key": "multiSample",
"label": "Anti Aliasing Samples",
"decimal": 0,
"minimum": 0,
"maximum": 32
},
{
"type": "boolean",
"key": "shadows",
"label": "Display Shadows"
},
{
"type": "boolean",
"key": "textures",
"label": "Display Textures"
},
{
"type": "boolean",
"key": "twoSidedLighting",
"label": "Two Sided Lighting"
},
{
"type": "boolean",
"key": "ssaoEnable",
"label": "Screen Space Ambient Occlusion"
},
{
"type": "splitter"
},
{
"type": "boolean",
"key": "cameras",
"label": "cameras"
},
{
"type": "boolean",
"key": "clipGhosts",
"label": "clipGhosts"
},
{
"type": "boolean",
"key": "controlVertices",
"label": "controlVertices"
},
{
"type": "boolean",
"key": "deformers",
"label": "deformers"
},
{
"type": "boolean",
"key": "dimensions",
"label": "dimensions"
},
{
"type": "boolean",
"key": "dynamicConstraints",
"label": "dynamicConstraints"
},
{
"type": "boolean",
"key": "dynamics",
"label": "dynamics"
},
{
"type": "boolean",
"key": "fluids",
"label": "fluids"
},
{
"type": "boolean",
"key": "follicles",
"label": "follicles"
},
{
"type": "boolean",
"key": "gpuCacheDisplayFilter",
"label": "gpuCacheDisplayFilter"
},
{
"type": "boolean",
"key": "greasePencils",
"label": "greasePencils"
},
{
"type": "boolean",
"key": "grid",
"label": "grid"
},
{
"type": "boolean",
"key": "hairSystems",
"label": "hairSystems"
},
{
"type": "boolean",
"key": "handles",
"label": "handles"
},
{
"type": "boolean",
"key": "hud",
"label": "hud"
},
{
"type": "boolean",
"key": "hulls",
"label": "hulls"
},
{
"type": "boolean",
"key": "ikHandles",
"label": "ikHandles"
},
{
"type": "boolean",
"key": "imagePlane",
"label": "imagePlane"
},
{
"type": "boolean",
"key": "joints",
"label": "joints"
},
{
"type": "boolean",
"key": "lights",
"label": "lights"
},
{
"type": "boolean",
"key": "locators",
"label": "locators"
},
{
"type": "boolean",
"key": "manipulators",
"label": "manipulators"
},
{
"type": "boolean",
"key": "motionTrails",
"label": "motionTrails"
},
{
"type": "boolean",
"key": "nCloths",
"label": "nCloths"
},
{
"type": "boolean",
"key": "nParticles",
"label": "nParticles"
},
{
"type": "boolean",
"key": "nRigids",
"label": "nRigids"
},
{
"type": "boolean",
"key": "nurbsCurves",
"label": "nurbsCurves"
},
{
"type": "boolean",
"key": "nurbsSurfaces",
"label": "nurbsSurfaces"
},
{
"type": "boolean",
"key": "particleInstancers",
"label": "particleInstancers"
},
{
"type": "boolean",
"key": "pivots",
"label": "pivots"
},
{
"type": "boolean",
"key": "planes",
"label": "planes"
},
{
"type": "boolean",
"key": "pluginShapes",
"label": "pluginShapes"
},
{
"type": "boolean",
"key": "polymeshes",
"label": "polymeshes"
},
{
"type": "boolean",
"key": "strokes",
"label": "strokes"
},
{
"type": "boolean",
"key": "subdivSurfaces",
"label": "subdivSurfaces"
}
]
},
{
"type": "dict",
"collapsible": true,
"key": "Camera Options",
"label": "Camera Options",
"children": [
{
"type": "boolean",
"key": "displayGateMask",
"label": "displayGateMask"
},
{
"type": "boolean",
"key": "displayResolution",
"label": "displayResolution"
},
{
"type": "boolean",
"key": "displayFilmGate",
"label": "displayFilmGate"
},
{
"type": "boolean",
"key": "displayFieldChart",
"label": "displayFieldChart"
},
{
"type": "boolean",
"key": "displaySafeAction",
"label": "displaySafeAction"
},
{
"type": "boolean",
"key": "displaySafeTitle",
"label": "displaySafeTitle"
},
{
"type": "boolean",
"key": "displayFilmPivot",
"label": "displayFilmPivot"
},
{
"type": "boolean",
"key": "displayFilmOrigin",
"label": "displayFilmOrigin"
},
{
"type": "number",
"key": "overscan",
"label": "overscan",
"decimal": 1,
"minimum": 0,
"maximum": 10
}
]
}
]
}
]
}
]

View file

@ -0,0 +1,35 @@
[
{
"__default_values__": {
"docstring": ""
}
},
{
"type": "dict",
"collapsible": true,
"key": "{key}",
"label": "{label}",
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "boolean",
"key": "optional",
"label": "Optional"
},
{
"type": "boolean",
"key": "active",
"label": "Active"
},
{
"type": "label",
"label": "{docstring}"
}
]
}
]

View file

@ -0,0 +1,495 @@
{
"key": "example_dict",
"label": "Examples",
"type": "dict",
"is_file": true,
"children": [
{
"type": "dict",
"key": "schema_template_exaples",
"label": "Schema template examples",
"children": [
{
"type": "schema_template",
"name": "example_template",
"template_data": {
"host_label": "Application 1",
"host_name": "app_1",
"multipath_executables": false
}
},
{
"type": "schema_template",
"name": "example_template",
"template_data": {
"host_label": "Application 2",
"host_name": "app_2"
}
}
]
},
{
"key": "env_group_test",
"label": "EnvGroup Test",
"type": "dict",
"children": [
{
"key": "key_to_store_in_system_settings",
"label": "Testing environment group",
"type": "raw-json",
"env_group_key": "test_group"
}
]
},
{
"key": "dict_wrapper",
"type": "dict",
"children": [
{
"type": "enum",
"key": "test_enum_singleselection",
"label": "Enum Single Selection",
"enum_items": [
{ "value_1": "Label 1" },
{ "value_2": "Label 2" },
{ "value_3": "Label 3" }
]
},
{
"type": "enum",
"key": "test_enum_multiselection",
"label": "Enum Multi Selection",
"multiselection": true,
"enum_items": [
{ "value_1": "Label 1" },
{ "value_2": "Label 2" },
{ "value_3": "Label 3" }
]
},
{
"type": "boolean",
"key": "bool",
"label": "Boolean checkbox"
},
{
"type": "label",
"label": "NOTE: This is label"
},
{
"type": "splitter"
},
{
"type": "number",
"key": "integer",
"label": "Integer",
"decimal": 0,
"minimum": 0,
"maximum": 10
},
{
"type": "number",
"key": "float",
"label": "Float (2 decimals)",
"decimal": 2,
"minimum": -10,
"maximum": -5
},
{
"type": "text",
"key": "singleline_text",
"label": "Singleline text"
},
{
"type": "text",
"key": "multiline_text",
"label": "Multiline text",
"multiline": true
},
{
"type": "raw-json",
"key": "raw_json",
"label": "Raw json input"
},
{
"type": "list",
"key": "list_item_of_multiline_texts",
"label": "List of multiline texts",
"object_type": {
"type": "text",
"multiline": true
}
},
{
"type": "list",
"key": "list_item_of_floats",
"label": "List of floats",
"object_type": {
"type": "number",
"decimal": 3,
"minimum": 1000,
"maximum": 2000
}
},
{
"type": "dict-modifiable",
"key": "modifiable_dict_of_integers",
"label": "Modifiable dict of integers",
"object_type": {
"type": "number",
"decimal": 0,
"minimum": 10,
"maximum": 100
}
},
{
"type": "dict-modifiable",
"key": "modifiable_dict_with_required_keys",
"label": "Modifiable dict with required keys",
"required_keys": [
"key_1",
"key_2"
],
"object_type": "text"
},
{
"type": "list-strict",
"key": "strict_list_labels_horizontal",
"label": "StrictList-labels-horizontal (color)",
"object_types": [
{
"label": "Red",
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
},
{
"label": "Green",
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
},
{
"label": "Blue",
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
},
{
"label": "Alpha",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 6
}
]
},
{
"type": "list-strict",
"key": "strict_list_labels_vertical",
"label": "StrictList-labels-vertical (color)",
"horizontal": false,
"object_types": [
{
"label": "Red",
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
},
{
"label": "Green",
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
},
{
"label": "Blue",
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
},
{
"label": "Alpha",
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 6
}
]
},
{
"type": "list-strict",
"key": "strict_list_nolabels_horizontal",
"label": "StrictList-nolabels-horizontal (color)",
"object_types": [
{
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
},
{
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
},
{
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
},
{
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 6
}
]
},
{
"type": "list-strict",
"key": "strict_list_nolabels_vertical",
"label": "StrictList-nolabels-vertical (color)",
"horizontal": false,
"object_types": [
{
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
},
{
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
},
{
"type": "number",
"minimum": 0,
"maximum": 255,
"decimal": 0
},
{
"type": "number",
"minimum": 0,
"maximum": 1,
"decimal": 6
}
]
},
{
"type": "list",
"key": "dict_item",
"label": "DictItem in List",
"object_type": {
"type": "dict",
"children": [
{
"key": "families",
"label": "Families",
"type": "list",
"object_type": "text"
},
{
"key": "hosts",
"label": "Hosts",
"type": "list",
"object_type": "text"
}
]
}
},
{
"type": "path",
"key": "single_path_input",
"label": "Single path input",
"multiplatform": false,
"multipath": false
},
{
"type": "path",
"key": "multi_path_input",
"label": "Multi path input",
"multiplatform": false,
"multipath": true
},
{
"type": "path",
"key": "single_os_specific_path_input",
"label": "Single OS specific path input",
"multiplatform": true,
"multipath": false
},
{
"type": "path",
"key": "multi_os_specific_path_input",
"label": "Multi OS specific path input",
"multiplatform": true,
"multipath": true
},
{
"key": "collapsible",
"type": "dict",
"label": "collapsible dictionary",
"collapsible": true,
"is_group": true,
"children": [
{
"type": "boolean",
"key": "_nothing",
"label": "Exmaple input"
}
]
},
{
"key": "collapsible_expanded",
"type": "dict",
"label": "collapsible dictionary, expanded on creation",
"collapsible": true,
"collapsed": false,
"is_group": true,
"children": [
{
"type": "boolean",
"key": "_nothing",
"label": "Exmaple input"
}
]
},
{
"key": "not_collapsible",
"type": "dict",
"label": "Not collapsible",
"collapsible": false,
"is_group": true,
"children": [
{
"type": "boolean",
"key": "_nothing",
"label": "Exmaple input"
}
]
},
{
"key": "nested_dict_lvl1",
"type": "dict",
"label": "Nested dictionary (level 1)",
"children": [
{
"key": "nested_dict_lvl2",
"type": "dict",
"label": "Nested dictionary (level 2)",
"is_group": true,
"children": [
{
"key": "nested_dict_lvl3",
"type": "dict",
"label": "Nested dictionary (level 3)",
"children": [
{
"type": "boolean",
"key": "_nothing",
"label": "Exmaple input"
}
]
},
{
"key": "nested_dict_lvl3_2",
"type": "dict",
"label": "Nested dictionary (level 3) (2)",
"children": [
{
"type": "text",
"key": "_nothing",
"label": "Exmaple input"
},
{
"type": "text",
"key": "_nothing2",
"label": "Exmaple input 2"
}
]
}
]
}
]
},
{
"key": "form_examples",
"type": "dict",
"label": "Form examples",
"children": [
{
"key": "inputs_without_form_example",
"type": "dict",
"label": "Inputs without form",
"children": [
{
"type": "text",
"key": "_nothing_1",
"label": "Example label"
},
{
"type": "text",
"key": "_nothing_2",
"label": "Example label ####"
},
{
"type": "text",
"key": "_nothing_3",
"label": "Example label ########"
}
]
},
{
"key": "inputs_with_form_example",
"type": "dict",
"label": "Inputs with form",
"children": [
{
"type": "form",
"children": [
{
"type": "text",
"key": "_nothing_1",
"label": "Example label"
},
{
"type": "text",
"key": "_nothing_2",
"label": "Example label ####"
},
{
"type": "text",
"key": "_nothing_3",
"label": "Example label ########"
}
]
}
]
}
]
},
{
"type": "collapsible-wrap",
"label": "Collapsible Wrapper without key",
"children": [
{
"type": "text",
"key": "_example_input_collapsible",
"label": "Example input in collapsible wrapper"
}
]
}
]
}
]
}

View file

@ -0,0 +1,20 @@
[
{
"__default_values__": {
"multipath_executables": true
}
},
{
"type": "raw-json",
"label": "{host_label} Environments",
"key": "{host_name}_environments",
"env_group_key": "{host_name}"
},
{
"type": "path",
"key": "{host_name}_executables",
"label": "{host_label} - Full paths to executables",
"multiplatform": "{multipath_executables}",
"multipath": true
}
]

View file

@ -0,0 +1,43 @@
{
"type": "dict",
"key": "aftereffects",
"label": "Adobe AfterEffects",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "schema_template",
"name": "template_host_unchangables"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
},
{
"type": "dict",
"key": "variants",
"children": [
{
"type": "schema_template",
"name": "template_host_variant",
"template_data": [
{
"app_variant_label": "2020",
"app_variant": "2020"
},
{
"app_variant_label": "2021",
"app_variant": "2021"
}
]
}
]
}
]
}

View file

@ -0,0 +1,43 @@
{
"type": "dict",
"key": "blender",
"label": "Blender",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "schema_template",
"name": "template_host_unchangables"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
},
{
"type": "dict",
"key": "variants",
"children": [
{
"type": "schema_template",
"name": "template_host_variant",
"template_data": [
{
"app_variant_label": "2.83",
"app_variant": "2-83"
},
{
"app_variant_label": "2.90",
"app_variant": "2-90"
}
]
}
]
}
]
}

View file

@ -0,0 +1,39 @@
{
"type": "dict",
"key": "celaction",
"label": "CelAction2D",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "schema_template",
"name": "template_host_unchangables"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
},
{
"type": "dict",
"key": "variants",
"children": [
{
"type": "schema_template",
"name": "template_host_variant",
"template_data": [
{
"app_variant_label": "Local",
"app_variant": "local"
}
]
}
]
}
]
}

View file

@ -0,0 +1,37 @@
{
"type": "dict",
"key": "djvview",
"label": "DJV View",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "schema_template",
"name": "template_host_unchangables"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
},
{
"type": "dict",
"key": "variants",
"children": [
{
"type": "schema_template",
"name": "template_host_variant",
"template_data": {
"app_variant_label": "1.1",
"app_variant": "1-1"
}
}
]
}
]
}

View file

@ -0,0 +1,43 @@
{
"type": "dict",
"key": "fusion",
"label": "Blackmagic Fusion",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "schema_template",
"name": "template_host_unchangables"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
},
{
"type": "dict",
"key": "variants",
"children": [
{
"type": "schema_template",
"name": "template_host_variant",
"template_data": [
{
"app_variant_label": "16",
"app_variant": "16"
},
{
"app_variant_label": "9",
"app_variant": "9"
}
]
}
]
}
]
}

View file

@ -0,0 +1,43 @@
{
"type": "dict",
"key": "harmony",
"label": "Toon Boom Harmony",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "schema_template",
"name": "template_host_unchangables"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
},
{
"type": "dict",
"key": "variants",
"children": [
{
"type": "schema_template",
"name": "template_host_variant",
"template_data": [
{
"app_variant_label": "20",
"app_variant": "20"
},
{
"app_variant_label": "17",
"app_variant": "17"
}
]
}
]
}
]
}

View file

@ -0,0 +1,47 @@
{
"type": "dict",
"key": "houdini",
"label": "SideFX Houdini",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "schema_template",
"name": "template_host_unchangables"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
},
{
"type": "dict",
"key": "variants",
"children": [
{
"type": "schema_template",
"name": "template_host_variant",
"template_data": [
{
"app_variant_label": "18.5",
"app_variant": "18-5"
},
{
"app_variant_label": "18",
"app_variant": "18"
},
{
"app_variant_label": "17",
"app_variant": "17"
}
]
}
]
}
]
}

View file

@ -0,0 +1,47 @@
{
"type": "dict",
"key": "maya",
"label": "Autodesk Maya",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "schema_template",
"name": "template_host_unchangables"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
},
{
"type": "dict",
"key": "variants",
"children": [
{
"type": "schema_template",
"name": "template_host_variant",
"template_data": [
{
"app_variant_label": "2020",
"app_variant": "2020"
},
{
"app_variant_label": "2019",
"app_variant": "2019"
},
{
"app_variant_label": "2018",
"app_variant": "2018"
}
]
}
]
}
]
}

View file

@ -0,0 +1,47 @@
{
"type": "dict",
"key": "mayabatch",
"label": "Autodesk Maya Batch",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "schema_template",
"name": "template_host_unchangables"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
},
{
"type": "dict",
"key": "variants",
"children": [
{
"type": "schema_template",
"name": "template_host_variant",
"template_data": [
{
"app_variant_label": "2020",
"app_variant": "2020"
},
{
"app_variant_label": "2019",
"app_variant": "2019"
},
{
"app_variant_label": "2018",
"app_variant": "2018"
}
]
}
]
}
]
}

View file

@ -0,0 +1,43 @@
{
"type": "dict",
"key": "photoshop",
"label": "Adobe Photoshop",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "schema_template",
"name": "template_host_unchangables"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
},
{
"type": "dict",
"key": "variants",
"children": [
{
"type": "schema_template",
"name": "template_host_variant",
"template_data": [
{
"app_variant_label": "2020",
"app_variant": "2020"
},
{
"app_variant_label": "2021",
"app_variant": "2021"
}
]
}
]
}
]
}

View file

@ -0,0 +1,39 @@
{
"type": "dict",
"key": "resolve",
"label": "Blackmagic DaVinci Resolve",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "schema_template",
"name": "template_host_unchangables"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
},
{
"type": "dict",
"key": "variants",
"children": [
{
"type": "schema_template",
"name": "template_host_variant",
"template_data": [
{
"app_variant_label": "16",
"app_variant": "16"
}
]
}
]
}
]
}

View file

@ -0,0 +1,43 @@
{
"type": "dict",
"key": "shell",
"label": "Shell",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
},
{
"type": "dict",
"key": "variants",
"children": [
{
"type": "schema_template",
"name": "template_host_variant",
"template_data": [
{
"app_variant": "python_3-7",
"app_variant_label": "Python 3.7"
},
{
"app_variant": "python_2-7",
"app_variant_label": "Python 2.7"
},
{
"app_variant": "terminal",
"app_variant_label": "Terminal"
}
]
}
]
}
]
}

View file

@ -0,0 +1,43 @@
{
"type": "dict",
"key": "tvpaint",
"label": "TVPaint",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "schema_template",
"name": "template_host_unchangables"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
},
{
"type": "dict",
"key": "variants",
"children": [
{
"type": "schema_template",
"name": "template_host_variant",
"template_data": [
{
"app_variant_label": "Animation 11 (64bits)",
"app_variant": "animation_11-64bits"
},
{
"app_variant_label": "Animation 11 (32bits)",
"app_variant": "animation_11-32bits"
}
]
}
]
}
]
}

View file

@ -0,0 +1,39 @@
{
"type": "dict",
"key": "unreal",
"label": "Unreal Editor",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "schema_template",
"name": "template_host_unchangables"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
},
{
"type": "dict",
"key": "variants",
"children": [
{
"type": "schema_template",
"name": "template_host_variant",
"template_data": [
{
"app_variant": "4-24",
"app_variant_label": "4.24"
}
]
}
]
}
]
}

View file

@ -0,0 +1,38 @@
[
{
"type": "text",
"key": "label",
"label": "Label",
"placeholder": "Host label (without any version)",
"roles": ["developer"]
},
{
"type": "text",
"key": "icon",
"label": "Icon",
"placeholder": "Host icon path template",
"roles": ["developer"]
},
{
"type": "enum",
"key": "host_name",
"label": "Host implementation",
"enum_items": [
{ "": "< without host >" },
{ "aftereffects": "aftereffects" },
{ "blender": "blender" },
{ "celaction": "celaction" },
{ "fusion": "fusion" },
{ "harmony": "harmony" },
{ "hiero": "hiero" },
{ "houdini": "houdini" },
{ "maya": "maya" },
{ "nuke": "nuke" },
{ "photoshop": "photoshop" },
{ "resolve": "resolve" },
{ "tvpaint": "tvpaint" },
{ "unreal": "unreal" }
],
"roles": ["developer"]
}
]

View file

@ -0,0 +1,63 @@
[
{
"type": "dict",
"key": "{app_variant}",
"label": "{app_variant_label}",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "text",
"key": "variant_label",
"label": "Variant label",
"placeholder": "Only \"Label\" is used if not filled."
},
{
"type": "path",
"key": "executables",
"label": "Executables",
"multiplatform": true,
"multipath": true
},
{
"type":"separator"
},
{
"type": "dict",
"key": "arguments",
"label": "Arguments",
"use_label_wrap": false,
"children": [
{
"key": "windows",
"label": "Windows",
"type": "list",
"object_type": "text"
},
{
"key": "darwin",
"label": "MacOS",
"type": "list",
"object_type": "text"
},
{
"key": "linux",
"label": "Linux",
"type": "list",
"object_type": "text"
}
]
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
}
]
}
]

View file

@ -0,0 +1,53 @@
[
{
"type": "dict",
"key": "{nuke_type}",
"label": "Foundry {nuke_label}",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "schema_template",
"name": "template_host_unchangables"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json"
},
{
"type": "dict",
"key": "variants",
"children": [
{
"type": "schema_template",
"name": "template_host_variant",
"template_data": [
{
"app_variant": "12-2",
"app_variant_label": "12.2"
},
{
"app_variant": "12-0",
"app_variant_label": "12.0"
},
{
"app_variant": "11-3",
"app_variant_label": "11.3"
},
{
"app_variant": "11-2",
"app_variant_label": "11.2"
}
]
}
]
}
]
}
]

View file

@ -0,0 +1,148 @@
{
"type": "dict",
"key": "ftrack",
"label": "Ftrack",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "text",
"key": "ftrack_server",
"label": "Server"
},
{
"type": "splitter"
},
{
"type": "label",
"label": "Additional Ftrack paths"
},
{
"type": "list",
"key": "ftrack_actions_path",
"label": "Action paths",
"object_type": "text"
},
{
"type": "list",
"key": "ftrack_events_path",
"label": "Event paths",
"object_type": "text"
},
{
"type": "separator"
},
{
"key": "intent",
"type": "dict",
"label": "Intent",
"collapsible_key": true,
"is_group": true,
"children": [
{
"type": "label",
"label": "Intent"
},
{
"type": "dict-modifiable",
"object_type": "text",
"key": "items"
},
{
"type": "separator"
},
{
"key": "default",
"type": "text",
"label": "Default Intent"
},
{
"type": "separator"
}
]
},
{
"key": "custom_attributes",
"label": "Custom Attributes",
"type": "dict",
"children": [
{
"type": "dict",
"key": "show",
"label": "Project Custom attributes",
"children": [
{
"type": "schema_template",
"name": "template_custom_attribute",
"template_data": [
{
"key": "avalon_auto_sync"
},
{
"key": "library_project"
},
{
"key": "applications"
}
]
}
]
},
{
"type": "dict",
"key": "is_hierarchical",
"label": "Hierarchical Attributes",
"children": [
{
"type": "schema_template",
"name": "template_custom_attribute",
"template_data": [
{
"key": "tools_env"
},
{
"key": "avalon_mongo_id"
},
{
"key": "fps"
},
{
"key": "frameStart"
},
{
"key": "frameEnd"
},
{
"key": "clipIn"
},
{
"key": "clipOut"
},
{
"key": "handleStart"
},
{
"key": "handleEnd"
},
{
"key": "resolutionWidth"
},
{
"key": "resolutionHeight"
},
{
"key": "pixelAspect"
}
]
}
]
}
]
}
]
}

View file

@ -0,0 +1,21 @@
[
{
"key": "{key}",
"label": "{key}",
"type": "dict",
"children": [
{
"key": "write_security_roles",
"label": "Write roles",
"type": "list",
"object_type": "text"
},
{
"key": "read_security_roles",
"label": "Read roles",
"type": "list",
"object_type": "text"
}
]
}
]

View file

@ -0,0 +1,97 @@
{
"key": "applications",
"type": "dict",
"label": "Applications",
"collapsible": true,
"is_file": true,
"children": [
{
"type": "schema",
"name": "schema_maya"
},
{
"type": "schema",
"name": "schema_mayabatch"
},
{
"type": "schema_template",
"name": "template_nuke",
"template_data": {
"nuke_type": "nuke",
"nuke_label": "Nuke"
}
},
{
"type": "schema_template",
"name": "template_nuke",
"template_data": {
"nuke_type": "nukex",
"nuke_label": "Nuke X"
}
},
{
"type": "schema_template",
"name": "template_nuke",
"template_data": {
"nuke_type": "nukestudio",
"nuke_label": "Nuke Studio"
}
},
{
"type": "schema_template",
"name": "template_nuke",
"template_data": {
"nuke_type": "hiero",
"nuke_label": "Hiero"
}
},
{
"type": "schema",
"name": "schema_fusion"
},
{
"type": "schema",
"name": "schema_resolve"
},
{
"type": "schema",
"name": "schema_houdini"
},
{
"type": "schema",
"name": "schema_blender"
},
{
"type": "schema",
"name": "schema_harmony"
},
{
"type": "schema",
"name": "schema_tvpaint"
},
{
"type": "schema",
"name": "schema_photoshop"
},
{
"type": "schema",
"name": "schema_aftereffects"
},
{
"type": "schema",
"name": "schema_celaction"
},
{
"type": "schema",
"name": "schema_unreal"
},
{
"type": "schema",
"name": "schema_shell"
},
{
"type": "schema",
"name": "schema_djv"
}
]
}

View file

@ -0,0 +1,38 @@
{
"key": "general",
"type": "dict",
"label": "General",
"collapsible": true,
"is_file": true,
"children": [
{
"key": "studio_name",
"type": "text",
"label": "Studio Name"
},
{
"key": "studio_code",
"type": "text",
"label": "Studio Short Code"
},
{
"type": "splitter"
},
{
"key": "environment",
"label": "Environment",
"type": "raw-json",
"env_group_key": "global"
},
{
"type": "splitter"
},
{
"type": "path",
"key": "openpype_path",
"label": "Versions Repository",
"multiplatform": true,
"multipath": true
}
]
}

View file

@ -0,0 +1,22 @@
{
"key": "system",
"type": "dict",
"children": [
{
"type": "schema",
"name": "schema_general"
},
{
"type": "schema",
"name": "schema_modules"
},
{
"type": "schema",
"name": "schema_applications"
},
{
"type": "schema",
"name": "schema_tools"
}
]
}

View file

@ -0,0 +1,195 @@
{
"key": "modules",
"type": "dict",
"label": "Modules",
"collapsible": true,
"is_file": true,
"children": [
{
"type": "dict",
"key": "avalon",
"label": "Avalon",
"collapsible": true,
"children": [
{
"type": "number",
"key": "AVALON_TIMEOUT",
"minimum": 0,
"label": "Avalon Mongo Timeout (ms)"
},
{
"type": "path",
"label": "Thumbnail Storage Location",
"key": "AVALON_THUMBNAIL_ROOT",
"multiplatform": true,
"multipath": false
}
]
},
{
"type": "schema",
"name": "schema_ftrack"
},
{
"type": "dict",
"key": "timers_manager",
"label": "Timers Manager",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "number",
"decimal": 2,
"key": "full_time",
"label": "Max idle time"
},
{
"type": "number",
"decimal": 2,
"key": "message_time",
"label": "When dialog will show"
}
]
},
{
"type": "dict",
"key": "clockify",
"label": "Clockify",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "text",
"key": "workspace_name",
"label": "Workspace name"
}
]
}, {
"type": "dict",
"key": "sync_server",
"label": "Sync Server",
"collapsible": true,
"checkbox_key": "enabled",
"children": [{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
}]
},{
"type": "dict",
"key": "deadline",
"label": "Deadline",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "text",
"key": "DEADLINE_REST_URL",
"label": "Deadline Resl URL"
}
]
},
{
"type": "dict",
"key": "muster",
"label": "Muster",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
},
{
"type": "text",
"key": "MUSTER_REST_URL",
"label": "Muster Rest URL"
},
{
"type": "dict-modifiable",
"object_type": {
"type": "number",
"minimum": 0,
"maximum": 300
},
"is_group": true,
"key": "templates_mapping",
"label": "Templates mapping",
"is_file": true
}
]
},
{
"type": "dict",
"key": "log_viewer",
"label": "Logging",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
}
]
},
{
"type": "dict",
"key": "user",
"label": "User setting",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
}
]
},
{
"type": "dict",
"key": "standalonepublish_tool",
"label": "Standalone Publish",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
}
]
},
{
"type": "dict",
"key": "idle_manager",
"label": "Idle Manager",
"collapsible": true,
"checkbox_key": "enabled",
"children": [
{
"type": "boolean",
"key": "enabled",
"label": "Enabled"
}
]
}
]
}

View file

@ -0,0 +1,35 @@
{
"type": "dict",
"key": "tools",
"collapsible": true,
"is_file": true,
"children": [
{
"type": "dict-modifiable",
"label": "Tools",
"key": "tool_groups",
"collapsible_key": true,
"object_type": {
"type": "dict",
"children": [
{
"key": "environment",
"label": "Environments",
"type": "raw-json"
},
{
"type": "separator"
},
{
"type": "dict-modifiable",
"key": "variants",
"collapsible_key": true,
"object_type": {
"type": "raw-json"
}
}
]
}
}
]
}

View file

@ -0,0 +1,603 @@
import os
import json
import copy
import logging
import collections
import datetime
from abc import ABCMeta, abstractmethod
import six
import openpype
from .constants import (
GLOBAL_SETTINGS_KEY,
SYSTEM_SETTINGS_KEY,
PROJECT_SETTINGS_KEY,
PROJECT_ANATOMY_KEY,
LOCAL_SETTING_KEY
)
from .lib import load_json_file
JSON_EXC = getattr(json.decoder, "JSONDecodeError", ValueError)
@six.add_metaclass(ABCMeta)
class SettingsHandler:
@abstractmethod
def save_studio_settings(self, data):
"""Save studio overrides of system settings.
Do not use to store whole system settings data with defaults but only
it's overrides with metadata defining how overrides should be applied
in load function. For loading should be used function
`studio_system_settings`.
Args:
data(dict): Data of studio overrides with override metadata.
"""
pass
@abstractmethod
def save_project_settings(self, project_name, overrides):
"""Save studio overrides of project settings.
Data are saved for specific project or as defaults for all projects.
Do not use to store whole project settings data with defaults but only
it's overrides with metadata defining how overrides should be applied
in load function. For loading should be used function
`get_studio_project_settings_overrides` for global project settings
and `get_project_settings_overrides` for project specific settings.
Args:
project_name(str, null): Project name for which overrides are
or None for global settings.
data(dict): Data of project overrides with override metadata.
"""
pass
@abstractmethod
def save_project_anatomy(self, project_name, anatomy_data):
"""Save studio overrides of project anatomy data.
Args:
project_name(str, null): Project name for which overrides are
or None for global settings.
data(dict): Data of project overrides with override metadata.
"""
pass
@abstractmethod
def get_studio_system_settings_overrides(self):
"""Studio overrides of system settings."""
pass
@abstractmethod
def get_studio_project_settings_overrides(self):
"""Studio overrides of default project settings."""
pass
@abstractmethod
def get_studio_project_anatomy_overrides(self):
"""Studio overrides of default project anatomy data."""
pass
@abstractmethod
def get_project_settings_overrides(self, project_name):
"""Studio overrides of project settings for specific project.
Args:
project_name(str): Name of project for which data should be loaded.
Returns:
dict: Only overrides for entered project, may be empty dictionary.
"""
pass
@abstractmethod
def get_project_anatomy_overrides(self, project_name):
"""Studio overrides of project anatomy for specific project.
Args:
project_name(str): Name of project for which data should be loaded.
Returns:
dict: Only overrides for entered project, may be empty dictionary.
"""
pass
@six.add_metaclass(ABCMeta)
class LocalSettingsHandler:
"""Handler that should handle about storing and loading of local settings.
Local settings are "workstation" specific modifications that modify how
system and project settings look on the workstation and only there.
"""
@abstractmethod
def save_local_settings(self, data):
"""Save local data of local settings.
Args:
data(dict): Data of local data with override metadata.
"""
pass
@abstractmethod
def get_local_settings(self):
"""Studio overrides of system settings."""
pass
class CacheValues:
cache_lifetime = 10
def __init__(self):
self.data = None
self.creation_time = None
def data_copy(self):
if not self.data:
return {}
return copy.deepcopy(self.data)
def update_data(self, data):
self.data = data
self.creation_time = datetime.datetime.now()
def update_from_document(self, document):
data = {}
if document:
if "data" in document:
data = document["data"]
elif "value" in document:
value = document["value"]
if value:
data = json.loads(value)
self.data = data
def to_json_string(self):
return json.dumps(self.data or {})
@property
def is_outdated(self):
if self.creation_time is None:
return True
delta = (datetime.datetime.now() - self.creation_time).seconds
return delta > self.cache_lifetime
class MongoSettingsHandler(SettingsHandler):
"""Settings handler that use mongo for storing and loading of settings."""
def __init__(self):
# Get mongo connection
from openpype.lib import OpenPypeMongoConnection
from avalon.api import AvalonMongoDB
settings_collection = OpenPypeMongoConnection.get_mongo_client()
self._anatomy_keys = None
self._attribute_keys = None
# TODO prepare version of pype
# - pype version should define how are settings saved and loaded
database_name = os.environ["OPENPYPE_DATABASE_NAME"]
# TODO modify to not use hardcoded keys
collection_name = "settings"
self.settings_collection = settings_collection
self.database_name = database_name
self.collection_name = collection_name
self.collection = settings_collection[database_name][collection_name]
self.avalon_db = AvalonMongoDB()
self.system_settings_cache = CacheValues()
self.project_settings_cache = collections.defaultdict(CacheValues)
self.project_anatomy_cache = collections.defaultdict(CacheValues)
def _prepare_project_settings_keys(self):
from .entities import ProjectSettings
# Prepare anatomy keys and attribute keys
# NOTE this is cached on first import
# - keys may change only on schema change which should not happen
# during production
project_settings_root = ProjectSettings(
reset=False, change_state=False
)
anatomy_entity = project_settings_root["project_anatomy"]
anatomy_keys = set(anatomy_entity.keys())
anatomy_keys.remove("attributes")
attribute_keys = set(anatomy_entity["attributes"].keys())
self._anatomy_keys = anatomy_keys
self._attribute_keys = attribute_keys
@property
def anatomy_keys(self):
if self._anatomy_keys is None:
self._prepare_project_settings_keys()
return self._anatomy_keys
@property
def attribute_keys(self):
if self._attribute_keys is None:
self._prepare_project_settings_keys()
return self._attribute_keys
def _prepare_global_settings(self, data):
output = {}
# Add "openpype_path" key to global settings if is set
if "general" in data and "openpype_path" in data["general"]:
output["openpype_path"] = data["general"]["openpype_path"]
return output
def save_studio_settings(self, data):
"""Save studio overrides of system settings.
Do not use to store whole system settings data with defaults but only
it's overrides with metadata defining how overrides should be applied
in load function. For loading should be used function
`studio_system_settings`.
Args:
data(dict): Data of studio overrides with override metadata.
"""
# Store system settings
self.system_settings_cache.update_data(data)
self.collection.replace_one(
{
"type": SYSTEM_SETTINGS_KEY
},
{
"type": SYSTEM_SETTINGS_KEY,
"data": self.system_settings_cache.data
},
upsert=True
)
# Get global settings from system settings
global_settings = self._prepare_global_settings(
self.system_settings_cache.data
)
# Store global settings
self.collection.replace_one(
{
"type": GLOBAL_SETTINGS_KEY
},
{
"type": GLOBAL_SETTINGS_KEY,
"data": global_settings
},
upsert=True
)
def save_project_settings(self, project_name, overrides):
"""Save studio overrides of project settings.
Data are saved for specific project or as defaults for all projects.
Do not use to store whole project settings data with defaults but only
it's overrides with metadata defining how overrides should be applied
in load function. For loading should be used function
`get_studio_project_settings_overrides` for global project settings
and `get_project_settings_overrides` for project specific settings.
Args:
project_name(str, null): Project name for which overrides are
or None for global settings.
data(dict): Data of project overrides with override metadata.
"""
data_cache = self.project_settings_cache[project_name]
data_cache.update_data(overrides)
self._save_project_data(
project_name, PROJECT_SETTINGS_KEY, data_cache
)
def save_project_anatomy(self, project_name, anatomy_data):
"""Save studio overrides of project anatomy data.
Args:
project_name(str, null): Project name for which overrides are
or None for global settings.
data(dict): Data of project overrides with override metadata.
"""
data_cache = self.project_anatomy_cache[project_name]
data_cache.update_data(anatomy_data)
if project_name is not None:
self._save_project_anatomy_data(project_name, data_cache)
else:
self._save_project_data(
project_name, PROJECT_ANATOMY_KEY, data_cache
)
@classmethod
def prepare_mongo_update_dict(cls, in_data):
data = {}
for key, value in in_data.items():
if not isinstance(value, dict):
data[key] = value
continue
new_value = cls.prepare_mongo_update_dict(value)
for _key, _value in new_value.items():
new_key = ".".join((key, _key))
data[new_key] = _value
return data
def _save_project_anatomy_data(self, project_name, data_cache):
# Create copy of data as they will be modified during save
new_data = data_cache.data_copy()
# Prepare avalon project document
collection = self.avalon_db.database[project_name]
project_doc = collection.find_one({
"type": "project"
})
if not project_doc:
raise ValueError((
"Project document of project \"{}\" does not exists."
" Create project first."
).format(project_name))
# Project's data
update_dict_data = {}
project_doc_data = project_doc.get("data") or {}
attributes = new_data.pop("attributes")
_applications = attributes.pop("applications", None) or []
for key, value in attributes.items():
if (
key in project_doc_data
and project_doc_data[key] == value
):
continue
update_dict_data[key] = value
update_dict_config = {}
applications = []
for application in _applications:
if not application:
continue
if isinstance(application, six.string_types):
applications.append({"name": application})
new_data["apps"] = applications
for key, value in new_data.items():
project_doc_value = project_doc.get(key)
if key in project_doc and project_doc_value == value:
continue
update_dict_config[key] = value
if not update_dict_data and not update_dict_config:
return
data_changes = self.prepare_mongo_update_dict(update_dict_data)
# Update dictionary of changes that will be changed in mongo
update_dict = {}
for key, value in data_changes.items():
new_key = "data.{}".format(key)
update_dict[new_key] = value
for key, value in update_dict_config.items():
new_key = "config.{}".format(key)
update_dict[new_key] = value
collection.update_one(
{"type": "project"},
{"$set": update_dict}
)
def _save_project_data(self, project_name, doc_type, data_cache):
is_default = bool(project_name is None)
replace_filter = {
"type": doc_type,
"is_default": is_default
}
replace_data = {
"type": doc_type,
"data": data_cache.data,
"is_default": is_default
}
if not is_default:
replace_filter["project_name"] = project_name
replace_data["project_name"] = project_name
self.collection.replace_one(
replace_filter,
replace_data,
upsert=True
)
def get_studio_system_settings_overrides(self):
"""Studio overrides of system settings."""
if self.system_settings_cache.is_outdated:
document = self.collection.find_one({
"type": SYSTEM_SETTINGS_KEY
})
self.system_settings_cache.update_from_document(document)
return self.system_settings_cache.data_copy()
def _get_project_settings_overrides(self, project_name):
if self.project_settings_cache[project_name].is_outdated:
document_filter = {
"type": PROJECT_SETTINGS_KEY,
}
if project_name is None:
document_filter["is_default"] = True
else:
document_filter["project_name"] = project_name
document = self.collection.find_one(document_filter)
self.project_settings_cache[project_name].update_from_document(
document
)
return self.project_settings_cache[project_name].data_copy()
def get_studio_project_settings_overrides(self):
"""Studio overrides of default project settings."""
return self._get_project_settings_overrides(None)
def get_project_settings_overrides(self, project_name):
"""Studio overrides of project settings for specific project.
Args:
project_name(str): Name of project for which data should be loaded.
Returns:
dict: Only overrides for entered project, may be empty dictionary.
"""
if not project_name:
return {}
return self._get_project_settings_overrides(project_name)
def project_doc_to_anatomy_data(self, project_doc):
"""Convert project document to anatomy data.
Probably should fill missing keys and values.
"""
if not project_doc:
return {}
attributes = {}
project_doc_data = project_doc.get("data") or {}
for key in self.attribute_keys:
value = project_doc_data.get(key)
if value is not None:
attributes[key] = value
project_doc_config = project_doc.get("config") or {}
app_names = set()
if "apps" in project_doc_config:
for app_item in project_doc_config.pop("apps"):
if not app_item:
continue
app_name = app_item.get("name")
if app_name:
app_names.add(app_name)
attributes["applications"] = list(app_names)
output = {"attributes": attributes}
for key in self.anatomy_keys:
value = project_doc_config.get(key)
if value is not None:
output[key] = value
return output
def _get_project_anatomy_overrides(self, project_name):
if self.project_anatomy_cache[project_name].is_outdated:
if project_name is None:
document_filter = {
"type": PROJECT_ANATOMY_KEY,
"is_default": True
}
document = self.collection.find_one(document_filter)
self.project_anatomy_cache[project_name].update_from_document(
document
)
else:
collection = self.avalon_db.database[project_name]
project_doc = collection.find_one({"type": "project"})
self.project_anatomy_cache[project_name].update_data(
self.project_doc_to_anatomy_data(project_doc)
)
return self.project_anatomy_cache[project_name].data_copy()
def get_studio_project_anatomy_overrides(self):
"""Studio overrides of default project anatomy data."""
return self._get_project_anatomy_overrides(None)
def get_project_anatomy_overrides(self, project_name):
"""Studio overrides of project anatomy for specific project.
Args:
project_name(str): Name of project for which data should be loaded.
Returns:
dict: Only overrides for entered project, may be empty dictionary.
"""
if not project_name:
return {}
return self._get_project_anatomy_overrides(project_name)
class MongoLocalSettingsHandler(LocalSettingsHandler):
"""Settings handler that use mongo for store and load local settings.
Data have 2 query criteria. First is key "type" stored in constant
`LOCAL_SETTING_KEY`. Second is key "site_id" which value can be obstained
with `get_local_site_id` function.
"""
def __init__(self, local_site_id=None):
# Get mongo connection
from openpype.lib import (
OpenPypeMongoConnection,
get_local_site_id
)
if local_site_id is None:
local_site_id = get_local_site_id()
settings_collection = OpenPypeMongoConnection.get_mongo_client()
# TODO prepare version of pype
# - pype version should define how are settings saved and loaded
database_name = os.environ["OPENPYPE_DATABASE_NAME"]
# TODO modify to not use hardcoded keys
collection_name = "settings"
self.settings_collection = settings_collection
self.database_name = database_name
self.collection_name = collection_name
self.collection = settings_collection[database_name][collection_name]
self.local_site_id = local_site_id
self.local_settings_cache = CacheValues()
def save_local_settings(self, data):
"""Save local settings.
Args:
data(dict): Data of studio overrides with override metadata.
"""
data = data or {}
self.local_settings_cache.update_data(data)
self.collection.replace_one(
{
"type": LOCAL_SETTING_KEY,
"site_id": self.local_site_id
},
{
"type": LOCAL_SETTING_KEY,
"site_id": self.local_site_id,
"data": self.local_settings_cache.data
},
upsert=True
)
def get_local_settings(self):
"""Local settings for local site id."""
if self.local_settings_cache.is_outdated:
document = self.collection.find_one({
"type": LOCAL_SETTING_KEY,
"site_id": self.local_site_id
})
self.local_settings_cache.update_from_document(document)
return self.local_settings_cache.data_copy()

786
openpype/settings/lib.py Normal file
View file

@ -0,0 +1,786 @@
import os
import json
import functools
import logging
import platform
import copy
from .constants import (
M_OVERRIDEN_KEY,
M_ENVIRONMENT_KEY,
METADATA_KEYS,
SYSTEM_SETTINGS_KEY,
PROJECT_SETTINGS_KEY,
PROJECT_ANATOMY_KEY,
DEFAULT_PROJECT_KEY
)
log = logging.getLogger(__name__)
# Py2 + Py3 json decode exception
JSON_EXC = getattr(json.decoder, "JSONDecodeError", ValueError)
# Path to default settings
DEFAULTS_DIR = os.path.join(
os.path.dirname(os.path.abspath(__file__)),
"defaults"
)
# Variable where cache of default settings are stored
_DEFAULT_SETTINGS = None
# Handler of studio overrides
_SETTINGS_HANDLER = None
# Handler of local settings
_LOCAL_SETTINGS_HANDLER = None
def require_handler(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
global _SETTINGS_HANDLER
if _SETTINGS_HANDLER is None:
_SETTINGS_HANDLER = create_settings_handler()
return func(*args, **kwargs)
return wrapper
def require_local_handler(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
global _LOCAL_SETTINGS_HANDLER
if _LOCAL_SETTINGS_HANDLER is None:
_LOCAL_SETTINGS_HANDLER = create_local_settings_handler()
return func(*args, **kwargs)
return wrapper
def create_settings_handler():
from .handlers import MongoSettingsHandler
# Handler can't be created in global space on initialization but only when
# needed. Plus here may be logic: Which handler is used (in future).
return MongoSettingsHandler()
def create_local_settings_handler():
from .handlers import MongoLocalSettingsHandler
return MongoLocalSettingsHandler()
def calculate_changes(old_value, new_value):
changes = {}
for key, value in new_value.items():
if key not in old_value:
changes[key] = value
continue
_value = old_value[key]
if isinstance(value, dict) and isinstance(_value, dict):
_changes = calculate_changes(_value, value)
if _changes:
changes[key] = _changes
continue
if _value != value:
changes[key] = value
return changes
@require_handler
def save_studio_settings(data):
"""Save studio overrides of system settings.
Triggers callbacks on modules that want to know about system settings
changes.
Callbacks are triggered on all modules. They must check if their enabled
value has changed.
For saving of data cares registered Settings handler.
Args:
data(dict): Overrides data with metadata defying studio overrides.
"""
# Notify Pype modules
from openpype.modules import ModulesManager, ISettingsChangeListener
old_data = get_system_settings()
default_values = get_default_settings()[SYSTEM_SETTINGS_KEY]
new_data = apply_overrides(default_values, copy.deepcopy(data))
clear_metadata_from_settings(new_data)
changes = calculate_changes(old_data, new_data)
modules_manager = ModulesManager(_system_settings=new_data)
for module in modules_manager.get_enabled_modules():
if isinstance(module, ISettingsChangeListener):
module.on_system_settings_save(old_data, new_data, changes)
return _SETTINGS_HANDLER.save_studio_settings(data)
@require_handler
def save_project_settings(project_name, overrides):
"""Save studio overrides of project settings.
Old value, new value and changes are passed to enabled modules that want to
know about settings changes.
For saving of data cares registered Settings handler.
Args:
project_name (str): Project name for which overrides are passed.
Default project's value is None.
overrides(dict): Overrides data with metadata defying studio overrides.
"""
# Notify Pype modules
from openpype.modules import ModulesManager, ISettingsChangeListener
default_values = get_default_settings()[PROJECT_SETTINGS_KEY]
if project_name:
old_data = get_project_settings(project_name)
studio_overrides = get_studio_project_settings_overrides()
studio_values = apply_overrides(default_values, studio_overrides)
clear_metadata_from_settings(studio_values)
new_data = apply_overrides(studio_values, copy.deepcopy(overrides))
else:
old_data = get_default_project_settings(exclude_locals=True)
new_data = apply_overrides(default_values, copy.deepcopy(overrides))
clear_metadata_from_settings(new_data)
changes = calculate_changes(old_data, new_data)
modules_manager = ModulesManager()
for module in modules_manager.get_enabled_modules():
if isinstance(module, ISettingsChangeListener):
module.on_project_settings_save(
old_data, new_data, project_name, changes
)
return _SETTINGS_HANDLER.save_project_settings(project_name, overrides)
@require_handler
def save_project_anatomy(project_name, anatomy_data):
"""Save studio overrides of project anatomy.
Old value, new value and changes are passed to enabled modules that want to
know about settings changes.
For saving of data cares registered Settings handler.
Args:
project_name (str): Project name for which overrides are passed.
Default project's value is None.
overrides(dict): Overrides data with metadata defying studio overrides.
"""
# Notify Pype modules
from openpype.modules import ModulesManager, ISettingsChangeListener
default_values = get_default_settings()[PROJECT_ANATOMY_KEY]
if project_name:
old_data = get_anatomy_settings(project_name)
studio_overrides = get_studio_project_settings_overrides()
studio_values = apply_overrides(default_values, studio_overrides)
clear_metadata_from_settings(studio_values)
new_data = apply_overrides(studio_values, copy.deepcopy(anatomy_data))
else:
old_data = get_default_anatomy_settings(exclude_locals=True)
new_data = apply_overrides(default_values, copy.deepcopy(anatomy_data))
clear_metadata_from_settings(new_data)
changes = calculate_changes(old_data, new_data)
modules_manager = ModulesManager()
for module in modules_manager.get_enabled_modules():
if isinstance(module, ISettingsChangeListener):
module.on_project_anatomy_save(
old_data, new_data, changes, project_name
)
return _SETTINGS_HANDLER.save_project_anatomy(project_name, anatomy_data)
@require_handler
def get_studio_system_settings_overrides():
return _SETTINGS_HANDLER.get_studio_system_settings_overrides()
@require_handler
def get_studio_project_settings_overrides():
return _SETTINGS_HANDLER.get_studio_project_settings_overrides()
@require_handler
def get_studio_project_anatomy_overrides():
return _SETTINGS_HANDLER.get_studio_project_anatomy_overrides()
@require_handler
def get_project_settings_overrides(project_name):
return _SETTINGS_HANDLER.get_project_settings_overrides(project_name)
@require_handler
def get_project_anatomy_overrides(project_name):
return _SETTINGS_HANDLER.get_project_anatomy_overrides(project_name)
@require_local_handler
def save_local_settings(data):
return _LOCAL_SETTINGS_HANDLER.save_local_settings(data)
@require_local_handler
def get_local_settings():
return _LOCAL_SETTINGS_HANDLER.get_local_settings()
class DuplicatedEnvGroups(Exception):
def __init__(self, duplicated):
self.origin_duplicated = duplicated
self.duplicated = {}
for key, items in duplicated.items():
self.duplicated[key] = []
for item in items:
self.duplicated[key].append("/".join(item["parents"]))
msg = "Duplicated environment group keys. {}".format(
", ".join([
"\"{}\"".format(env_key) for env_key in self.duplicated.keys()
])
)
super(DuplicatedEnvGroups, self).__init__(msg)
def reset_default_settings():
global _DEFAULT_SETTINGS
_DEFAULT_SETTINGS = None
def get_default_settings():
# TODO add cacher
return load_jsons_from_dir(DEFAULTS_DIR)
# global _DEFAULT_SETTINGS
# if _DEFAULT_SETTINGS is None:
# _DEFAULT_SETTINGS = load_jsons_from_dir(DEFAULTS_DIR)
# return copy.deepcopy(_DEFAULT_SETTINGS)
def load_json_file(fpath):
# Load json data
try:
with open(fpath, "r") as opened_file:
return json.load(opened_file)
except JSON_EXC:
log.warning(
"File has invalid json format \"{}\"".format(fpath),
exc_info=True
)
return {}
def load_jsons_from_dir(path, *args, **kwargs):
"""Load all .json files with content from entered folder path.
Data are loaded recursively from a directory and recreate the
hierarchy as a dictionary.
Entered path hiearchy:
|_ folder1
| |_ data1.json
|_ folder2
|_ subfolder1
|_ data2.json
Will result in:
```javascript
{
"folder1": {
"data1": "CONTENT OF FILE"
},
"folder2": {
"data1": {
"subfolder1": "CONTENT OF FILE"
}
}
}
```
Args:
path (str): Path to the root folder where the json hierarchy starts.
Returns:
dict: Loaded data.
"""
output = {}
path = os.path.normpath(path)
if not os.path.exists(path):
# TODO warning
return output
sub_keys = list(kwargs.pop("subkeys", args))
for sub_key in tuple(sub_keys):
_path = os.path.join(path, sub_key)
if not os.path.exists(_path):
break
path = _path
sub_keys.pop(0)
base_len = len(path) + 1
for base, _directories, filenames in os.walk(path):
base_items_str = base[base_len:]
if not base_items_str:
base_items = []
else:
base_items = base_items_str.split(os.path.sep)
for filename in filenames:
basename, ext = os.path.splitext(filename)
if ext == ".json":
full_path = os.path.join(base, filename)
value = load_json_file(full_path)
dict_keys = base_items + [basename]
output = subkey_merge(output, value, dict_keys)
for sub_key in sub_keys:
output = output[sub_key]
return output
def find_environments(data, with_items=False, parents=None):
""" Find environemnt values from system settings by it's metadata.
Args:
data(dict): System settings data or dictionary which may contain
environments metadata.
Returns:
dict: Key as Environment key and value for `acre` module.
"""
if not data or not isinstance(data, dict):
return {}
output = {}
if parents is None:
parents = []
if M_ENVIRONMENT_KEY in data:
metadata = data.get(M_ENVIRONMENT_KEY)
for env_group_key, env_keys in metadata.items():
if env_group_key not in output:
output[env_group_key] = []
_env_values = {}
for key in env_keys:
_env_values[key] = data[key]
item = {
"env": _env_values,
"parents": parents[:-1]
}
output[env_group_key].append(item)
for key, value in data.items():
_parents = copy.deepcopy(parents)
_parents.append(key)
result = find_environments(value, True, _parents)
if not result:
continue
for env_group_key, env_values in result.items():
if env_group_key not in output:
output[env_group_key] = []
for env_values_item in env_values:
output[env_group_key].append(env_values_item)
if with_items:
return output
duplicated_env_groups = {}
final_output = {}
for key, value_in_list in output.items():
if len(value_in_list) > 1:
duplicated_env_groups[key] = value_in_list
else:
final_output[key] = value_in_list[0]["env"]
if duplicated_env_groups:
raise DuplicatedEnvGroups(duplicated_env_groups)
return final_output
def subkey_merge(_dict, value, keys):
key = keys.pop(0)
if not keys:
_dict[key] = value
return _dict
if key not in _dict:
_dict[key] = {}
_dict[key] = subkey_merge(_dict[key], value, keys)
return _dict
def merge_overrides(source_dict, override_dict):
"""Merge data from override_dict to source_dict."""
if M_OVERRIDEN_KEY in override_dict:
overriden_keys = set(override_dict.pop(M_OVERRIDEN_KEY))
else:
overriden_keys = set()
for key, value in override_dict.items():
if (key in overriden_keys or key not in source_dict):
source_dict[key] = value
elif isinstance(value, dict) and isinstance(source_dict[key], dict):
source_dict[key] = merge_overrides(source_dict[key], value)
else:
source_dict[key] = value
return source_dict
def apply_overrides(source_data, override_data):
if not override_data:
return source_data
_source_data = copy.deepcopy(source_data)
return merge_overrides(_source_data, override_data)
def apply_local_settings_on_system_settings(system_settings, local_settings):
"""Apply local settings on studio system settings.
ATM local settings can modify only application executables. Executable
values are not overriden but prepended.
"""
if not local_settings or "applications" not in local_settings:
return
current_platform = platform.system().lower()
for app_group_name, value in local_settings["applications"].items():
if not value or app_group_name not in system_settings["applications"]:
continue
variants = system_settings["applications"][app_group_name]["variants"]
for app_name, app_value in value.items():
if not app_value or app_name not in variants:
continue
executable = app_value.get("executable")
if not executable:
continue
platform_executables = variants[app_name]["executables"].get(
current_platform
)
# TODO This is temporary fix until launch arguments will be stored
# per platform and not per executable.
# - local settings store only executable
new_executables = [[executable, ""]]
new_executables.extend(platform_executables)
variants[app_name]["executables"] = new_executables
def apply_local_settings_on_anatomy_settings(
anatomy_settings, local_settings, project_name, site_name=None
):
"""Apply local settings on anatomy settings.
ATM local settings can modify project roots. Project name is required as
local settings have data stored data by project's name.
Local settings override root values in this order:
1.) Check if local settings contain overrides for default project and
apply it's values on roots if there are any.
2.) If passed `project_name` is not None then check project specific
overrides in local settings for the project and apply it's value on
roots if there are any.
NOTE: Root values of default project from local settings are always applied
if are set.
Args:
anatomy_settings (dict): Data for anatomy settings.
local_settings (dict): Data of local settings.
project_name (str): Name of project for which anatomy data are.
"""
if not local_settings:
return
local_project_settings = local_settings.get("projects") or {}
# Check for roots existence in local settings first
roots_project_locals = (
local_project_settings
.get(project_name, {})
)
roots_default_locals = (
local_project_settings
.get(DEFAULT_PROJECT_KEY, {})
)
# Skip rest of processing if roots are not set
if not roots_project_locals and not roots_default_locals:
return
# Get active site from settings
if site_name is None:
if project_name:
project_settings = get_project_settings(project_name)
else:
project_settings = get_default_project_settings()
site_name = (
project_settings["global"]["sync_server"]["config"]["active_site"]
)
# QUESTION should raise an exception?
if not site_name:
return
# Combine roots from local settings
roots_locals = roots_default_locals.get(site_name) or {}
roots_locals.update(roots_project_locals.get(site_name) or {})
# Skip processing if roots for current active site are not available in
# local settings
if not roots_locals:
return
current_platform = platform.system().lower()
root_data = anatomy_settings["roots"]
for root_name, path in roots_locals.items():
if root_name not in root_data:
continue
anatomy_settings["roots"][root_name][current_platform] = (
path
)
def get_site_local_overrides(project_name, site_name, local_settings=None):
"""Site overrides from local settings for passet project and site name.
Args:
project_name (str): For which project are overrides.
site_name (str): For which site are overrides needed.
local_settings (dict): Preloaded local settings. They are loaded
automatically if not passed.
"""
# Check if local settings were passed
if local_settings is None:
local_settings = get_local_settings()
output = {}
# Skip if local settings are empty
if not local_settings:
return output
local_project_settings = local_settings.get("projects") or {}
# Prepare overrides for entered project and for default project
project_locals = None
if project_name:
project_locals = local_project_settings.get(project_name)
default_project_locals = local_project_settings.get(DEFAULT_PROJECT_KEY)
# First load and use local settings from default project
if default_project_locals and site_name in default_project_locals:
output.update(default_project_locals[site_name])
# Apply project specific local settings if there are any
if project_locals and site_name in project_locals:
output.update(project_locals[site_name])
return output
def apply_local_settings_on_project_settings(
project_settings, local_settings, project_name
):
"""Apply local settings on project settings.
Currently is modifying active site and remote site in sync server.
Args:
project_settings (dict): Data for project settings.
local_settings (dict): Data of local settings.
project_name (str): Name of project for which settings data are.
"""
if not local_settings:
return
local_project_settings = local_settings.get("projects")
if not local_project_settings:
return
project_locals = local_project_settings.get(project_name) or {}
default_locals = local_project_settings.get(DEFAULT_PROJECT_KEY) or {}
active_site = (
project_locals.get("active_site")
or default_locals.get("active_site")
)
remote_site = (
project_locals.get("remote_site")
or default_locals.get("remote_site")
)
sync_server_config = project_settings["global"]["sync_server"]["config"]
if active_site:
sync_server_config["active_site"] = active_site
if remote_site:
sync_server_config["remote_site"] = remote_site
def get_system_settings(clear_metadata=True):
"""System settings with applied studio overrides."""
default_values = get_default_settings()[SYSTEM_SETTINGS_KEY]
studio_values = get_studio_system_settings_overrides()
result = apply_overrides(default_values, studio_values)
if clear_metadata:
clear_metadata_from_settings(result)
# TODO local settings may be required to apply for environments
local_settings = get_local_settings()
apply_local_settings_on_system_settings(result, local_settings)
return result
def get_default_project_settings(clear_metadata=True, exclude_locals=False):
"""Project settings with applied studio's default project overrides."""
default_values = get_default_settings()[PROJECT_SETTINGS_KEY]
studio_values = get_studio_project_settings_overrides()
result = apply_overrides(default_values, studio_values)
if clear_metadata:
clear_metadata_from_settings(result)
if not exclude_locals:
local_settings = get_local_settings()
apply_local_settings_on_project_settings(
result, local_settings, None
)
return result
def get_default_anatomy_settings(clear_metadata=True, exclude_locals=False):
"""Project anatomy data with applied studio's default project overrides."""
default_values = get_default_settings()[PROJECT_ANATOMY_KEY]
studio_values = get_studio_project_anatomy_overrides()
# TODO uncomment and remove hotfix result when overrides of anatomy
# are stored correctly.
result = apply_overrides(default_values, studio_values)
if clear_metadata:
clear_metadata_from_settings(result)
if not exclude_locals:
local_settings = get_local_settings()
apply_local_settings_on_anatomy_settings(
result, local_settings, None
)
return result
def get_anatomy_settings(project_name, site_name=None, exclude_locals=False):
"""Project anatomy data with applied studio and project overrides."""
if not project_name:
raise ValueError(
"Must enter project name. Call "
"`get_default_anatomy_settings` to get project defaults."
)
studio_overrides = get_default_anatomy_settings(False)
project_overrides = get_project_anatomy_overrides(
project_name
)
result = copy.deepcopy(studio_overrides)
if project_overrides:
for key, value in project_overrides.items():
result[key] = value
clear_metadata_from_settings(result)
if not exclude_locals:
local_settings = get_local_settings()
apply_local_settings_on_anatomy_settings(
result, local_settings, project_name, site_name
)
return result
def get_project_settings(project_name, exclude_locals=False):
"""Project settings with applied studio and project overrides."""
if not project_name:
raise ValueError(
"Must enter project name."
" Call `get_default_project_settings` to get project defaults."
)
studio_overrides = get_default_project_settings(False)
project_overrides = get_project_settings_overrides(
project_name
)
result = apply_overrides(studio_overrides, project_overrides)
clear_metadata_from_settings(result)
if not exclude_locals:
local_settings = get_local_settings()
apply_local_settings_on_project_settings(
result, local_settings, project_name
)
return result
def get_current_project_settings():
"""Project settings for current context project.
Project name should be stored in environment variable `AVALON_PROJECT`.
This function should be used only in host context where environment
variable must be set and should not happen that any part of process will
change the value of the enviornment variable.
"""
project_name = os.environ.get("AVALON_PROJECT")
if not project_name:
raise ValueError(
"Missing context project in environemt variable `AVALON_PROJECT`."
)
return get_project_settings(project_name)
def get_environments():
"""Calculated environment based on defaults and system settings.
Any default environment also found in the system settings will be fully
overriden by the one from the system settings.
Returns:
dict: Output should be ready for `acre` module.
"""
return find_environments(get_system_settings(False))
def clear_metadata_from_settings(values):
"""Remove all metadata keys from loaded settings."""
if isinstance(values, dict):
for key in tuple(values.keys()):
if key in METADATA_KEYS:
values.pop(key)
else:
clear_metadata_from_settings(values[key])
elif isinstance(values, list):
for item in values:
clear_metadata_from_settings(item)

View file

@ -0,0 +1,79 @@
# Structure of local settings
- local settings do not have any validation schemas right now this should help to see what is stored to local settings and how it works
- they are stored by identifier site_id which should be unified identifier of workstation
- all keys may and may not available on load
- contain main categories: `general`, `applications`, `projects`
## Categories
### General
- ATM contain only label of site
```json
{
"general": {
"site_label": "MySite"
}
}
```
### Applications
- modifications of application executables
- output should match application groups and variants
```json
{
"applications": {
"<app group>": {
"<app name>": {
"executable": "/my/path/to/nuke_12_2"
}
}
}
}
```
### Projects
- project specific modifications
- default project is stored under constant key defined in `pype.settings.contants`
```json
{
"projects": {
"<project name>": {
"active_site": "<name of active site>",
"remote_site": "<name of remote site>",
"roots": {
"<site name>": {
"<root name>": "<root dir path>"
}
}
}
}
}
```
## Final document
```json
{
"_id": "<ObjectId(...)>",
"site_id": "<site id>",
"general": {
"site_label": "MySite"
},
"applications": {
"<app group>": {
"<app name>": {
"executable": "<path to app executable>"
}
}
},
"projects": {
"<project name>": {
"active_site": "<name of active site>",
"remote_site": "<name of remote site>",
"roots": {
"<site name>": {
"<root name>": "<root dir path>"
}
}
}
}
}
```