diff --git a/pype/api.py b/pype/api.py index c722757a3c..021080b4d5 100644 --- a/pype/api.py +++ b/pype/api.py @@ -1,3 +1,7 @@ +from .settings import ( + system_settings, + project_settings +) from pypeapp import ( Logger, Anatomy, @@ -49,6 +53,9 @@ from .lib import ( from .lib import _subprocess as subprocess __all__ = [ + "system_settings", + "project_settings", + "Logger", "Anatomy", "project_overrides_dir_path", diff --git a/pype/settings/__init__.py b/pype/settings/__init__.py new file mode 100644 index 0000000000..7e73d541a4 --- /dev/null +++ b/pype/settings/__init__.py @@ -0,0 +1,9 @@ +from .lib import ( + system_settings, + project_settings +) + +__all__ = ( + "system_settings", + "project_settings" +) diff --git a/pype/settings/defaults/project_anatomy/colorspace.json b/pype/settings/defaults/project_anatomy/colorspace.json new file mode 100644 index 0000000000..8b934f810d --- /dev/null +++ b/pype/settings/defaults/project_anatomy/colorspace.json @@ -0,0 +1,42 @@ +{ + "nuke": { + "root": { + "colorManagement": "Nuke", + "OCIO_config": "nuke-default", + "defaultViewerLUT": "Nuke Root LUTs", + "monitorLut": "sRGB", + "int8Lut": "sRGB", + "int16Lut": "sRGB", + "logLut": "Cineon", + "floatLut": "linear" + }, + "viewer": { + "viewerProcess": "sRGB" + }, + "write": { + "render": { + "colorspace": "linear" + }, + "prerender": { + "colorspace": "linear" + }, + "still": { + "colorspace": "sRGB" + } + }, + "read": { + "[^-a-zA-Z0-9]beauty[^-a-zA-Z0-9]": "linear", + "[^-a-zA-Z0-9](P|N|Z|crypto)[^-a-zA-Z0-9]": "linear", + "[^-a-zA-Z0-9](plateRef)[^-a-zA-Z0-9]": "sRGB" + } + }, + "maya": { + + }, + "houdini": { + + }, + "resolve": { + + } +} diff --git a/pype/settings/defaults/project_anatomy/dataflow.json b/pype/settings/defaults/project_anatomy/dataflow.json new file mode 100644 index 0000000000..d2f470b5bc --- /dev/null +++ b/pype/settings/defaults/project_anatomy/dataflow.json @@ -0,0 +1,55 @@ +{ + "nuke": { + "nodes": { + "connected": true, + "modifymetadata": { + "_id": "connect_metadata", + "_previous": "ENDING", + "metadata.set.pype_studio_name": "{PYPE_STUDIO_NAME}", + "metadata.set.avalon_project_name": "{AVALON_PROJECT}", + "metadata.set.avalon_project_code": "{PYPE_STUDIO_CODE}", + "metadata.set.avalon_asset_name": "{AVALON_ASSET}" + }, + "crop": { + "_id": "connect_crop", + "_previous": "connect_metadata", + "box": [ + "{metadata.crop.x}", + "{metadata.crop.y}", + "{metadata.crop.right}", + "{metadata.crop.top}" + ] + }, + "write": { + "render": { + "_id": "output_write", + "_previous": "connect_crop", + "file_type": "exr", + "datatype": "16 bit half", + "compression": "Zip (1 scanline)", + "autocrop": true, + "tile_color": "0xff0000ff", + "channels": "rgb" + }, + "prerender": { + "_id": "output_write", + "_previous": "connect_crop", + "file_type": "exr", + "datatype": "16 bit half", + "compression": "Zip (1 scanline)", + "autocrop": false, + "tile_color": "0xc9892aff", + "channels": "rgba" + }, + "still": { + "_previous": "connect_crop", + "channels": "rgba", + "file_type": "tiff", + "datatype": "16 bit", + "compression": "LZW", + "tile_color": "0x4145afff" + } + } + } + } +} diff --git a/pype/settings/defaults/project_anatomy/roots.json b/pype/settings/defaults/project_anatomy/roots.json new file mode 100644 index 0000000000..0282471a60 --- /dev/null +++ b/pype/settings/defaults/project_anatomy/roots.json @@ -0,0 +1,5 @@ +{ + "windows": "C:/projects", + "linux": "/mnt/share/projects", + "darwin": "/Volumes/path" +} diff --git a/pype/settings/defaults/project_anatomy/templates.json b/pype/settings/defaults/project_anatomy/templates.json new file mode 100644 index 0000000000..0fff0265b3 --- /dev/null +++ b/pype/settings/defaults/project_anatomy/templates.json @@ -0,0 +1,30 @@ +{ + "version_padding": 3, + "version": "v{version:0>{@version_padding}}", + "frame_padding": 4, + "frame": "{frame:0>{@frame_padding}}", + "work": { + "folder": "{root}/{project[name]}/{hierarchy}/{asset}/work/{task}", + "file": "{project[code]}_{asset}_{task}_{@version}<_{comment}>.{ext}", + "path": "{@folder}/{@file}" + }, + "render": { + "folder": "{root}/{project[name]}/{hierarchy}/{asset}/publish/render/{subset}/{@version}", + "file": "{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{representation}", + "path": "{@folder}/{@file}" + }, + "texture": { + "path": "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}" + }, + "publish": { + "folder": "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/{@version}", + "file": "{project[code]}_{asset}_{subset}_{@version}<_{output}><.{@frame}>.{representation}", + "path": "{@folder}/{@file}", + "thumbnail": "{thumbnail_root}/{project[name]}/{_id}_{thumbnail_type}{ext}" + }, + "master": { + "folder": "{root}/{project[name]}/{hierarchy}/{asset}/publish/{family}/{subset}/master", + "file": "{project[code]}_{asset}_{subset}_master<_{output}><.{frame}>.{representation}", + "path": "{@folder}/{@file}" + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/ftrack/ftrack_config.json b/pype/settings/defaults/project_settings/ftrack/ftrack_config.json new file mode 100644 index 0000000000..1ef3a9d69f --- /dev/null +++ b/pype/settings/defaults/project_settings/ftrack/ftrack_config.json @@ -0,0 +1,16 @@ +{ + "sync_to_avalon": { + "statuses_name_change": ["not ready", "ready"] + }, + + "status_update": { + "_ignore_": ["in progress", "ommited", "on hold"], + "Ready": ["not ready"], + "In Progress" : ["_any_"] + }, + "status_version_to_task": { + "__description__": "Status `from` (key) must be lowered!", + "in progress": "in progress", + "approved": "approved" + } +} diff --git a/pype/settings/defaults/project_settings/ftrack/ftrack_custom_attributes.json b/pype/settings/defaults/project_settings/ftrack/ftrack_custom_attributes.json new file mode 100644 index 0000000000..f03d473cd0 --- /dev/null +++ b/pype/settings/defaults/project_settings/ftrack/ftrack_custom_attributes.json @@ -0,0 +1,165 @@ +[{ + "label": "FPS", + "key": "fps", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "write_security_role": ["ALL"], + "read_security_role": ["ALL"], + "default": null, + "config": { + "isdecimal": true + } +}, { + "label": "Applications", + "key": "applications", + "type": "enumerator", + "entity_type": "show", + "group": "avalon", + "config": { + "multiselect": true, + "data": [ + {"blender_2.80": "Blender 2.80"}, + {"blender_2.81": "Blender 2.81"}, + {"blender_2.82": "Blender 2.82"}, + {"blender_2.83": "Blender 2.83"}, + {"celaction_local": "CelAction2D Local"}, + {"maya_2017": "Maya 2017"}, + {"maya_2018": "Maya 2018"}, + {"maya_2019": "Maya 2019"}, + {"nuke_10.0": "Nuke 10.0"}, + {"nuke_11.2": "Nuke 11.2"}, + {"nuke_11.3": "Nuke 11.3"}, + {"nuke_12.0": "Nuke 12.0"}, + {"nukex_10.0": "NukeX 10.0"}, + {"nukex_11.2": "NukeX 11.2"}, + {"nukex_11.3": "NukeX 11.3"}, + {"nukex_12.0": "NukeX 12.0"}, + {"nukestudio_10.0": "NukeStudio 10.0"}, + {"nukestudio_11.2": "NukeStudio 11.2"}, + {"nukestudio_11.3": "NukeStudio 11.3"}, + {"nukestudio_12.0": "NukeStudio 12.0"}, + {"harmony_17": "Harmony 17"}, + {"houdini_16.5": "Houdini 16.5"}, + {"houdini_17": "Houdini 17"}, + {"houdini_18": "Houdini 18"}, + {"photoshop_2020": "Photoshop 2020"}, + {"python_3": "Python 3"}, + {"python_2": "Python 2"}, + {"premiere_2019": "Premiere Pro 2019"}, + {"premiere_2020": "Premiere Pro 2020"}, + {"resolve_16": "BM DaVinci Resolve 16"} + ] + } +}, { + "label": "Avalon auto-sync", + "key": "avalon_auto_sync", + "type": "boolean", + "entity_type": "show", + "group": "avalon", + "write_security_role": ["API", "Administrator"], + "read_security_role": ["API", "Administrator"] +}, { + "label": "Intent", + "key": "intent", + "type": "enumerator", + "entity_type": "assetversion", + "group": "avalon", + "config": { + "multiselect": false, + "data": [ + {"test": "Test"}, + {"wip": "WIP"}, + {"final": "Final"} + ] + } +}, { + "label": "Library Project", + "key": "library_project", + "type": "boolean", + "entity_type": "show", + "group": "avalon", + "write_security_role": ["API", "Administrator"], + "read_security_role": ["API", "Administrator"] +}, { + "label": "Clip in", + "key": "clipIn", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "default": null +}, { + "label": "Clip out", + "key": "clipOut", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "default": null +}, { + "label": "Frame start", + "key": "frameStart", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "default": null +}, { + "label": "Frame end", + "key": "frameEnd", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "default": null +}, { + "label": "Tools", + "key": "tools_env", + "type": "enumerator", + "is_hierarchical": true, + "group": "avalon", + "config": { + "multiselect": true, + "data": [ + {"mtoa_3.0.1": "mtoa_3.0.1"}, + {"mtoa_3.1.1": "mtoa_3.1.1"}, + {"mtoa_3.2.0": "mtoa_3.2.0"}, + {"yeti_2.1.2": "yeti_2.1"} + ] + } +}, { + "label": "Resolution Width", + "key": "resolutionWidth", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "default": null +}, { + "label": "Resolution Height", + "key": "resolutionHeight", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "default": null +}, { + "label": "Pixel aspect", + "key": "pixelAspect", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "config": { + "isdecimal": true + } +}, { + "label": "Frame handles start", + "key": "handleStart", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "default": null +}, { + "label": "Frame handles end", + "key": "handleEnd", + "type": "number", + "is_hierarchical": true, + "group": "avalon", + "default": null +} +] diff --git a/pype/settings/defaults/project_settings/ftrack/partnership_ftrack_cred.json b/pype/settings/defaults/project_settings/ftrack/partnership_ftrack_cred.json new file mode 100644 index 0000000000..6b3a32f181 --- /dev/null +++ b/pype/settings/defaults/project_settings/ftrack/partnership_ftrack_cred.json @@ -0,0 +1,5 @@ +{ + "server_url": "", + "api_key": "", + "api_user": "" +} diff --git a/pype/settings/defaults/project_settings/ftrack/plugins/server.json b/pype/settings/defaults/project_settings/ftrack/plugins/server.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/settings/defaults/project_settings/ftrack/plugins/server.json @@ -0,0 +1 @@ +{} diff --git a/pype/settings/defaults/project_settings/ftrack/plugins/user.json b/pype/settings/defaults/project_settings/ftrack/plugins/user.json new file mode 100644 index 0000000000..1ba8e9b511 --- /dev/null +++ b/pype/settings/defaults/project_settings/ftrack/plugins/user.json @@ -0,0 +1,5 @@ +{ + "TestAction": { + "ignore_me": true + } +} diff --git a/pype/settings/defaults/project_settings/ftrack/project_defaults.json b/pype/settings/defaults/project_settings/ftrack/project_defaults.json new file mode 100644 index 0000000000..a4e3aa3362 --- /dev/null +++ b/pype/settings/defaults/project_settings/ftrack/project_defaults.json @@ -0,0 +1,18 @@ +{ + "fps": 25, + "frameStart": 1001, + "frameEnd": 1100, + "clipIn": 1001, + "clipOut": 1100, + "handleStart": 10, + "handleEnd": 10, + + "resolutionHeight": 1080, + "resolutionWidth": 1920, + "pixelAspect": 1.0, + "applications": [ + "maya_2019", "nuke_11.3", "nukex_11.3", "nukestudio_11.3", "deadline" + ], + "tools_env": [], + "avalon_auto_sync": true +} diff --git a/pype/settings/defaults/project_settings/global/creator.json b/pype/settings/defaults/project_settings/global/creator.json new file mode 100644 index 0000000000..d14e779f01 --- /dev/null +++ b/pype/settings/defaults/project_settings/global/creator.json @@ -0,0 +1,8 @@ +{ + "Model": ["model"], + "Render Globals": ["light", "render"], + "Layout": ["layout"], + "Set Dress": ["setdress"], + "Look": ["look"], + "Rig": ["rigging"] +} diff --git a/pype/settings/defaults/project_settings/global/project_folder_structure.json b/pype/settings/defaults/project_settings/global/project_folder_structure.json new file mode 100644 index 0000000000..83bd5f12a9 --- /dev/null +++ b/pype/settings/defaults/project_settings/global/project_folder_structure.json @@ -0,0 +1,22 @@ +{ + "__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]": {} + } + } +} diff --git a/pype/settings/defaults/project_settings/global/sw_folders.json b/pype/settings/defaults/project_settings/global/sw_folders.json new file mode 100644 index 0000000000..a154935dce --- /dev/null +++ b/pype/settings/defaults/project_settings/global/sw_folders.json @@ -0,0 +1,8 @@ +{ + "compositing": ["nuke", "ae"], + "modeling": ["maya", "app2"], + "lookdev": ["substance"], + "animation": [], + "lighting": [], + "rigging": [] +} diff --git a/pype/settings/defaults/project_settings/global/workfiles.json b/pype/settings/defaults/project_settings/global/workfiles.json new file mode 100644 index 0000000000..393b2e3c10 --- /dev/null +++ b/pype/settings/defaults/project_settings/global/workfiles.json @@ -0,0 +1,7 @@ +{ + "last_workfile_on_startup": [ + { + "enabled": false + } + ] +} diff --git a/pype/settings/defaults/project_settings/maya/capture.json b/pype/settings/defaults/project_settings/maya/capture.json new file mode 100644 index 0000000000..b6c4893034 --- /dev/null +++ b/pype/settings/defaults/project_settings/maya/capture.json @@ -0,0 +1,108 @@ +{ + "Codec": { + "compression": "jpg", + "format": "image", + "quality": 95 + }, + "Display Options": { + "background": [ + 0.7137254901960784, + 0.7137254901960784, + 0.7137254901960784 + ], + "backgroundBottom": [ + 0.7137254901960784, + 0.7137254901960784, + 0.7137254901960784 + ], + "backgroundTop": [ + 0.7137254901960784, + 0.7137254901960784, + 0.7137254901960784 + ], + "override_display": true + }, + "Generic": { + "isolate_view": true, + "off_screen": true + }, + "IO": { + "name": "", + "open_finished": false, + "raw_frame_numbers": false, + "recent_playblasts": [], + "save_file": false + }, + "PanZoom": { + "pan_zoom": true + }, + "Renderer": { + "rendererName": "vp2Renderer" + }, + "Resolution": { + "height": 1080, + "mode": "Custom", + "percent": 1.0, + "width": 1920 + }, + "Time Range": { + "end_frame": 25, + "frame": "", + "start_frame": 0, + "time": "Time Slider" + }, + "Viewport Options": { + "cameras": false, + "clipGhosts": false, + "controlVertices": false, + "deformers": false, + "dimensions": false, + "displayLights": 0, + "dynamicConstraints": false, + "dynamics": false, + "fluids": false, + "follicles": false, + "gpuCacheDisplayFilter": false, + "greasePencils": false, + "grid": false, + "hairSystems": false, + "handles": false, + "high_quality": true, + "hud": false, + "hulls": false, + "ikHandles": false, + "imagePlane": false, + "joints": false, + "lights": false, + "locators": false, + "manipulators": false, + "motionTrails": false, + "nCloths": false, + "nParticles": false, + "nRigids": false, + "nurbsCurves": false, + "nurbsSurfaces": false, + "override_viewport_options": true, + "particleInstancers": false, + "pivots": false, + "planes": false, + "pluginShapes": false, + "polymeshes": true, + "shadows": false, + "strokes": false, + "subdivSurfaces": false, + "textures": false, + "twoSidedLighting": true + }, + "Camera Options": { + "displayGateMask": false, + "displayResolution": false, + "displayFilmGate": false, + "displayFieldChart": false, + "displaySafeAction": false, + "displaySafeTitle": false, + "displayFilmPivot": false, + "displayFilmOrigin": false, + "overscan": 1.0 + } +} diff --git a/pype/settings/defaults/project_settings/muster/templates_mapping.json b/pype/settings/defaults/project_settings/muster/templates_mapping.json new file mode 100644 index 0000000000..4edab9077d --- /dev/null +++ b/pype/settings/defaults/project_settings/muster/templates_mapping.json @@ -0,0 +1,19 @@ +{ + "3delight": 41, + "arnold": 46, + "arnold_sf": 57, + "gelato": 30, + "harware": 3, + "krakatoa": 51, + "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 +} diff --git a/pype/settings/defaults/project_settings/plugins/celaction/publish.json b/pype/settings/defaults/project_settings/plugins/celaction/publish.json new file mode 100644 index 0000000000..fd1af23d84 --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/celaction/publish.json @@ -0,0 +1,11 @@ +{ + "ExtractCelactionDeadline": { + "enabled": true, + "deadline_department": "", + "deadline_priority": 50, + "deadline_pool": "", + "deadline_pool_secondary": "", + "deadline_group": "", + "deadline_chunk_size": 10 + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/config.json b/pype/settings/defaults/project_settings/plugins/config.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/config.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/ftrack/publish.json b/pype/settings/defaults/project_settings/plugins/ftrack/publish.json new file mode 100644 index 0000000000..d8d93a36ee --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/ftrack/publish.json @@ -0,0 +1,7 @@ +{ + "IntegrateFtrackNote": { + "enabled": false, + "note_with_intent_template": "{intent}: {comment}", + "note_labels": [] + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/global/create.json b/pype/settings/defaults/project_settings/plugins/global/create.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/global/create.json @@ -0,0 +1 @@ +{} diff --git a/pype/settings/defaults/project_settings/plugins/global/filter.json b/pype/settings/defaults/project_settings/plugins/global/filter.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/global/filter.json @@ -0,0 +1 @@ +{} diff --git a/pype/settings/defaults/project_settings/plugins/global/load.json b/pype/settings/defaults/project_settings/plugins/global/load.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/global/load.json @@ -0,0 +1 @@ +{} diff --git a/pype/settings/defaults/project_settings/plugins/global/publish.json b/pype/settings/defaults/project_settings/plugins/global/publish.json new file mode 100644 index 0000000000..b946ac4b32 --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/global/publish.json @@ -0,0 +1,98 @@ +{ + "IntegrateMasterVersion": { + "enabled": false + }, + "ExtractJpegEXR": { + "enabled": true, + "ffmpeg_args": { + "input": [ + "-gamma 2.2" + ], + "output": [] + } + }, + "ExtractReview": { + "enabled": true, + "profiles": [ + { + "families": [], + "hosts": [], + "outputs": { + "h264": { + "filter": { + "families": [ + "render", + "review", + "ftrack" + ] + }, + "ext": "mp4", + "ffmpeg_args": { + "input": [ + "-gamma 2.2" + ], + "video_filters": [], + "audio_filters": [], + "output": [ + "-pix_fmt yuv420p", + "-crf 18", + "-intra" + ] + }, + "tags": [ + "burnin", + "ftrackreview" + ] + } + } + } + ] + }, + "ExtractBurnin": { + "enabled": false, + "options": { + "font_size": 42, + "opacity": 1, + "bg_opacity": 0, + "x_offset": 5, + "y_offset": 5, + "bg_padding": 5 + }, + "fields": {}, + "profiles": [ + { + "burnins": { + "burnin": { + "TOP_LEFT": "{yy}-{mm}-{dd}", + "TOP_RIGHT": "{anatomy[version]}", + "TOP_CENTERED": "", + "BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}", + "BOTTOM_CENTERED": "{asset}", + "BOTTOM_LEFT": "{username}" + } + } + } + ] + }, + "IntegrateAssetNew": { + "template_name_profiles": { + "publish": { + "families": [], + "tasks": [] + }, + "render": { + "families": [ + "review", + "render", + "prerender" + ] + } + } + }, + "ProcessSubmittedJobOnFarm": { + "enabled": false, + "deadline_department": "", + "deadline_pool": "", + "deadline_group": "" + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/maya/create.json b/pype/settings/defaults/project_settings/plugins/maya/create.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/maya/create.json @@ -0,0 +1 @@ +{} diff --git a/pype/settings/defaults/project_settings/plugins/maya/filter.json b/pype/settings/defaults/project_settings/plugins/maya/filter.json new file mode 100644 index 0000000000..83d6f05f31 --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/maya/filter.json @@ -0,0 +1,9 @@ +{ + "Preset n1": { + "ValidateNoAnimation": false, + "ValidateShapeDefaultNames": false + }, + "Preset n2": { + "ValidateNoAnimation": false + } +} diff --git a/pype/settings/defaults/project_settings/plugins/maya/load.json b/pype/settings/defaults/project_settings/plugins/maya/load.json new file mode 100644 index 0000000000..260fbb35ee --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/maya/load.json @@ -0,0 +1,18 @@ +{ + "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.8, 0.5] + } +} diff --git a/pype/settings/defaults/project_settings/plugins/maya/publish.json b/pype/settings/defaults/project_settings/plugins/maya/publish.json new file mode 100644 index 0000000000..2b3637ff80 --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/maya/publish.json @@ -0,0 +1,17 @@ +{ + "ValidateModelName": { + "enabled": false, + "material_file": "/path/to/shader_name_definition.txt", + "regex": "(.*)_(\\d)*_(?P.*)_(GEO)" + }, + "ValidateAssemblyName": { + "enabled": false + }, + "ValidateShaderName": { + "enabled": false, + "regex": "(?P.*)_(.*)_SHD" + }, + "ValidateMeshHasOverlappingUVs": { + "enabled": false + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/maya/workfile_build.json b/pype/settings/defaults/project_settings/plugins/maya/workfile_build.json new file mode 100644 index 0000000000..443bc2cb2c --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/maya/workfile_build.json @@ -0,0 +1,136 @@ +[ + { + "tasks": [ + "lighting" + ], + "current_context": [ + { + "subset_name_filters": [ + ".+[Mm]ain" + ], + "families": [ + "model" + ], + "repre_names": [ + "abc", + "ma" + ], + "loaders": [ + "ReferenceLoader" + ] + }, + { + "families": [ + "animation", + "pointcache" + ], + "repre_names": [ + "abc" + ], + "loaders": [ + "ReferenceLoader" + ] + }, + { + "families": [ + "rendersetup" + ], + "repre_names": [ + "json" + ], + "loaders": [ + "RenderSetupLoader" + ] + }, + { + "families": [ + "camera" + ], + "repre_names": [ + "abc" + ], + "loaders": [ + "ReferenceLoader" + ] + } + ], + "linked_assets": [ + { + "families": [ + "setdress" + ], + "repre_names": [ + "ma" + ], + "loaders": [ + "ReferenceLoader" + ] + }, + { + "families": [ + "ass" + ], + "repre_names": [ + "ass" + ], + "loaders": [ + "assLoader" + ] + } + ] + }, + { + "tasks": [ + "animation" + ], + "current_context": [ + { + "families": [ + "camera" + ], + "repre_names": [ + "abc", + "ma" + ], + "loaders": [ + "ReferenceLoader" + ] + }, + { + "families": [ + "audio" + ], + "repre_names": [ + "wav" + ], + "loaders": [ + "RenderSetupLoader" + ] + } + ], + "linked_assets": [ + { + "families": [ + "setdress" + ], + "repre_names": [ + "proxy" + ], + "loaders": [ + "ReferenceLoader" + ] + }, + { + "families": [ + "rig" + ], + "repre_names": [ + "ass" + ], + "loaders": [ + "rigLoader" + ] + } + ] + } +] \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/nuke/create.json b/pype/settings/defaults/project_settings/plugins/nuke/create.json new file mode 100644 index 0000000000..79ab665696 --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/nuke/create.json @@ -0,0 +1,8 @@ +{ + "CreateWriteRender": { + "fpath_template": "{work}/renders/nuke/{subset}/{subset}.{frame}.{ext}" + }, + "CreateWritePrerender": { + "fpath_template": "{work}/prerenders/nuke/{subset}/{subset}.{frame}.{ext}" + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/nuke/load.json b/pype/settings/defaults/project_settings/plugins/nuke/load.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/nuke/load.json @@ -0,0 +1 @@ +{} diff --git a/pype/settings/defaults/project_settings/plugins/nuke/publish.json b/pype/settings/defaults/project_settings/plugins/nuke/publish.json new file mode 100644 index 0000000000..08a099a0a0 --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/nuke/publish.json @@ -0,0 +1,53 @@ +{ + "ExtractThumbnail": { + "enabled": true, + "nodes": { + "Reformat": [ + [ + "type", + "to format" + ], + [ + "format", + "HD_1080" + ], + [ + "filter", + "Lanczos6" + ], + [ + "black_outside", + true + ], + [ + "pbb", + false + ] + ] + } + }, + "ValidateNukeWriteKnobs": { + "enabled": false, + "knobs": { + "render": { + "review": true + } + } + }, + "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 + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/nuke/workfile_build.json b/pype/settings/defaults/project_settings/plugins/nuke/workfile_build.json new file mode 100644 index 0000000000..4b48b46184 --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/nuke/workfile_build.json @@ -0,0 +1,23 @@ +[ + { + "tasks": [ + "compositing" + ], + "current_context": [ + { + "families": [ + "render", + "plate" + ], + "repre_names": [ + "exr", + "dpx" + ], + "loaders": [ + "LoadSequence" + ] + } + ], + "linked_assets": [] + } +] \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/nukestudio/filter.json b/pype/settings/defaults/project_settings/plugins/nukestudio/filter.json new file mode 100644 index 0000000000..bd6a0dc1bd --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/nukestudio/filter.json @@ -0,0 +1,10 @@ +{ + "strict": { + "ValidateVersion": true, + "VersionUpWorkfile": true + }, + "benevolent": { + "ValidateVersion": false, + "VersionUpWorkfile": false + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/nukestudio/publish.json b/pype/settings/defaults/project_settings/plugins/nukestudio/publish.json new file mode 100644 index 0000000000..d99a878c35 --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/nukestudio/publish.json @@ -0,0 +1,9 @@ +{ + "CollectInstanceVersion": { + "enabled": false + }, + "ExtractReviewCutUpVideo": { + "enabled": true, + "tags_addition": [] + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/resolve/create.json b/pype/settings/defaults/project_settings/plugins/resolve/create.json new file mode 100644 index 0000000000..8ff5b15714 --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/resolve/create.json @@ -0,0 +1,7 @@ +{ + "CreateShotClip": { + "clipName": "{track}{sequence}{shot}", + "folder": "takes", + "steps": 20 + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/standalonepublisher/publish.json b/pype/settings/defaults/project_settings/plugins/standalonepublisher/publish.json new file mode 100644 index 0000000000..2f1a3e7aca --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/standalonepublisher/publish.json @@ -0,0 +1,27 @@ +{ + "ExtractThumbnailSP": { + "ffmpeg_args": { + "input": [ + "-gamma 2.2" + ], + "output": [] + } + }, + "ExtractReviewSP": { + "outputs": { + "h264": { + "input": [ + "-gamma 2.2" + ], + "output": [ + "-pix_fmt yuv420p", + "-crf 18" + ], + "tags": [ + "preview" + ], + "ext": "mov" + } + } + } +} \ No newline at end of file diff --git a/pype/settings/defaults/project_settings/plugins/test/create.json b/pype/settings/defaults/project_settings/plugins/test/create.json new file mode 100644 index 0000000000..fa0b2fc05f --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/test/create.json @@ -0,0 +1,8 @@ +{ + "MyTestCreator": { + "my_test_property": "B", + "active": false, + "new_property": "new", + "family": "new_family" + } +} diff --git a/pype/settings/defaults/project_settings/plugins/test/publish.json b/pype/settings/defaults/project_settings/plugins/test/publish.json new file mode 100644 index 0000000000..3180dd5d8a --- /dev/null +++ b/pype/settings/defaults/project_settings/plugins/test/publish.json @@ -0,0 +1,10 @@ +{ + "MyTestPlugin": { + "label": "loaded from preset", + "optional": true, + "families": ["changed", "by", "preset"] + }, + "MyTestRemovedPlugin": { + "enabled": false + } +} diff --git a/pype/settings/defaults/project_settings/premiere/asset_default.json b/pype/settings/defaults/project_settings/premiere/asset_default.json new file mode 100644 index 0000000000..84d2bde3d8 --- /dev/null +++ b/pype/settings/defaults/project_settings/premiere/asset_default.json @@ -0,0 +1,5 @@ +{ + "frameStart": 1001, + "handleStart": 0, + "handleEnd": 0 +} diff --git a/pype/settings/defaults/project_settings/premiere/rules_tasks.json b/pype/settings/defaults/project_settings/premiere/rules_tasks.json new file mode 100644 index 0000000000..333c9cd70b --- /dev/null +++ b/pype/settings/defaults/project_settings/premiere/rules_tasks.json @@ -0,0 +1,21 @@ +{ + "defaultTasks": ["Layout", "Animation"], + "taskToSubsets": { + "Layout": ["reference", "audio"], + "Animation": ["audio"] + }, + "subsetToRepresentations": { + "reference": { + "preset": "h264", + "representation": "mp4" + }, + "thumbnail": { + "preset": "jpeg_thumb", + "representation": "jpg" + }, + "audio": { + "preset": "48khz", + "representation": "wav" + } + } +} diff --git a/pype/settings/defaults/project_settings/standalonepublisher/families.json b/pype/settings/defaults/project_settings/standalonepublisher/families.json new file mode 100644 index 0000000000..d05941cc26 --- /dev/null +++ b/pype/settings/defaults/project_settings/standalonepublisher/families.json @@ -0,0 +1,90 @@ +{ + "create_look": { + "name": "look", + "label": "Look", + "family": "look", + "icon": "paint-brush", + "defaults": ["Main"], + "help": "Shader connections defining shape look" + }, + "create_model": { + "name": "model", + "label": "Model", + "family": "model", + "icon": "cube", + "defaults": ["Main", "Proxy", "Sculpt"], + "help": "Polygonal static geometry" + }, + "create_workfile": { + "name": "workfile", + "label": "Workfile", + "family": "workfile", + "icon": "cube", + "defaults": ["Main"], + "help": "Working scene backup" + }, + "create_camera": { + "name": "camera", + "label": "Camera", + "family": "camera", + "icon": "video-camera", + "defaults": ["Main"], + "help": "Single baked camera" + }, + "create_pointcache": { + "name": "pointcache", + "label": "Pointcache", + "family": "pointcache", + "icon": "gears", + "defaults": ["Main"], + "help": "Alembic pointcache for animated data" + }, + "create_rig": { + "name": "rig", + "label": "Rig", + "family": "rig", + "icon": "wheelchair", + "defaults": ["Main"], + "help": "Artist-friendly rig with controls" + }, + "create_layout": { + "name": "layout", + "label": "Layout", + "family": "layout", + "icon": "cubes", + "defaults": ["Main"], + "help": "Simple scene for animators with camera" + }, + "create_plate": { + "name": "plate", + "label": "Plate", + "family": "plate", + "icon": "camera", + "defaults": ["Main", "BG", "Reference"], + "help": "Plates for compositors" + }, + "create_matchmove": { + "name": "matchmove", + "label": "Matchmove script", + "family": "matchmove", + "icon": "empire", + "defaults": ["Camera", "Object", "Mocap"], + "help": "Script exported from matchmoving application" + }, + "create_images": { + "name": "image", + "label": "Image file", + "family": "image", + "icon": "image", + "defaults": ["ConceptArt", "Reference", "Texture", "MattePaint"], + "help": "Holder for all kinds of image data" + }, + "create_editorial": { + "name": "editorial", + "label": "Editorial", + "family": "editorial", + "icon": "image", + "defaults": ["Main"], + "help": "Editorial files to generate shots." + } +} diff --git a/pype/settings/defaults/project_settings/tools/slates/example_HD.json b/pype/settings/defaults/project_settings/tools/slates/example_HD.json new file mode 100644 index 0000000000..b06391fb63 --- /dev/null +++ b/pype/settings/defaults/project_settings/tools/slates/example_HD.json @@ -0,0 +1,212 @@ +{ + "width": 1920, + "height": 1080, + "destination_path": "{destination_path}", + "style": { + "*": { + "font-family": "arial", + "font-color": "#ffffff", + "font-bold": false, + "font-italic": false, + "bg-color": "#0077ff", + "alignment-horizontal": "left", + "alignment-vertical": "top" + }, + "layer": { + "padding": 0, + "margin": 0 + }, + "rectangle": { + "padding": 0, + "margin": 0, + "bg-color": "#E9324B", + "fill": true + }, + "main_frame": { + "padding": 0, + "margin": 0, + "bg-color": "#252525" + }, + "table": { + "padding": 0, + "margin": 0, + "bg-color": "transparent" + }, + "table-item": { + "padding": 5, + "padding-bottom": 10, + "margin": 0, + "bg-color": "#212121", + "bg-alter-color": "#272727", + "font-color": "#dcdcdc", + "font-bold": false, + "font-italic": false, + "alignment-horizontal": "left", + "alignment-vertical": "top", + "word-wrap": false, + "ellide": true, + "max-lines": 1 + }, + "table-item-col[0]": { + "font-size": 20, + "font-color": "#898989", + "font-bold": true, + "ellide": false, + "word-wrap": true, + "max-lines": null + }, + "table-item-col[1]": { + "font-size": 40, + "padding-left": 10 + }, + "#colorbar": { + "bg-color": "#9932CC" + } + }, + "items": [{ + "type": "layer", + "direction": 1, + "name": "MainLayer", + "style": { + "#MainLayer": { + "width": 1094, + "height": 1000, + "margin": 25, + "padding": 0 + }, + "#LeftSide": { + "margin-right": 25 + } + }, + "items": [{ + "type": "layer", + "name": "LeftSide", + "items": [{ + "type": "layer", + "direction": 1, + "style": { + "table-item": { + "bg-color": "transparent", + "padding-bottom": 20 + }, + "table-item-col[0]": { + "font-size": 20, + "font-color": "#898989", + "alignment-horizontal": "right" + }, + "table-item-col[1]": { + "alignment-horizontal": "left", + "font-bold": true, + "font-size": 40 + } + }, + "items": [{ + "type": "table", + "values": [ + ["Show:", "{project[name]}"] + ], + "style": { + "table-item-field[0:0]": { + "width": 150 + }, + "table-item-field[0:1]": { + "width": 580 + } + } + }, { + "type": "table", + "values": [ + ["Submitting For:", "{intent}"] + ], + "style": { + "table-item-field[0:0]": { + "width": 160 + }, + "table-item-field[0:1]": { + "width": 218, + "alignment-horizontal": "right" + } + } + }] + }, { + "type": "rectangle", + "style": { + "bg-color": "#bc1015", + "width": 1108, + "height": 5, + "fill": true + } + }, { + "type": "table", + "use_alternate_color": true, + "values": [ + ["Version name:", "{version_name}"], + ["Date:", "{date}"], + ["Shot Types:", "{shot_type}"], + ["Submission Note:", "{submission_note}"] + ], + "style": { + "table-item": { + "padding-bottom": 20 + }, + "table-item-field[0:1]": { + "font-bold": true + }, + "table-item-field[3:0]": { + "word-wrap": true, + "ellide": true, + "max-lines": 4 + }, + "table-item-col[0]": { + "alignment-horizontal": "right", + "width": 150 + }, + "table-item-col[1]": { + "alignment-horizontal": "left", + "width": 958 + } + } + }] + }, { + "type": "layer", + "name": "RightSide", + "items": [{ + "type": "placeholder", + "name": "thumbnail", + "path": "{thumbnail_path}", + "style": { + "width": 730, + "height": 412 + } + }, { + "type": "placeholder", + "name": "colorbar", + "path": "{color_bar_path}", + "return_data": true, + "style": { + "width": 730, + "height": 55 + } + }, { + "type": "table", + "use_alternate_color": true, + "values": [ + ["Vendor:", "{vendor}"], + ["Shot Name:", "{shot_name}"], + ["Frames:", "{frame_start} - {frame_end} ({duration})"] + ], + "style": { + "table-item-col[0]": { + "alignment-horizontal": "left", + "width": 200 + }, + "table-item-col[1]": { + "alignment-horizontal": "right", + "width": 530, + "font-size": 30 + } + } + }] + }] + }] +} diff --git a/pype/settings/defaults/project_settings/unreal/project_setup.json b/pype/settings/defaults/project_settings/unreal/project_setup.json new file mode 100644 index 0000000000..8a4dffc526 --- /dev/null +++ b/pype/settings/defaults/project_settings/unreal/project_setup.json @@ -0,0 +1,4 @@ +{ + "dev_mode": false, + "install_unreal_python_engine": false +} diff --git a/pype/settings/defaults/system_settings/environments/avalon.json b/pype/settings/defaults/system_settings/environments/avalon.json new file mode 100644 index 0000000000..832ba07e71 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/avalon.json @@ -0,0 +1,16 @@ +{ + "AVALON_CONFIG": "pype", + "AVALON_PROJECTS": "{PYPE_PROJECTS_PATH}", + "AVALON_USERNAME": "avalon", + "AVALON_PASSWORD": "secret", + "AVALON_DEBUG": "1", + "AVALON_MONGO": "mongodb://localhost:2707", + "AVALON_DB": "avalon", + "AVALON_DB_DATA": "{PYPE_SETUP_PATH}/../mongo_db_data", + "AVALON_EARLY_ADOPTER": "1", + "AVALON_SCHEMA": "{PYPE_MODULE_ROOT}/schema", + "AVALON_LOCATION": "http://127.0.0.1", + "AVALON_LABEL": "Pype", + "AVALON_TIMEOUT": "1000", + "AVALON_THUMBNAIL_ROOT": "" +} diff --git a/pype/settings/defaults/system_settings/environments/blender.json b/pype/settings/defaults/system_settings/environments/blender.json new file mode 100644 index 0000000000..6f4f6a012d --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/blender.json @@ -0,0 +1,7 @@ +{ + "BLENDER_USER_SCRIPTS": "{PYPE_SETUP_PATH}/repos/avalon-core/setup/blender", + "PYTHONPATH": [ + "{PYPE_SETUP_PATH}/repos/avalon-core/setup/blender", + "{PYTHONPATH}" + ] +} diff --git a/pype/settings/defaults/system_settings/environments/celaction.json b/pype/settings/defaults/system_settings/environments/celaction.json new file mode 100644 index 0000000000..cdd4e609ab --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/celaction.json @@ -0,0 +1,3 @@ +{ + "CELACTION_TEMPLATE": "{PYPE_MODULE_ROOT}/pype/hosts/celaction/celaction_template_scene.scn" +} diff --git a/pype/settings/defaults/system_settings/environments/deadline.json b/pype/settings/defaults/system_settings/environments/deadline.json new file mode 100644 index 0000000000..e8ef52805b --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/deadline.json @@ -0,0 +1,3 @@ +{ + "DEADLINE_REST_URL": "http://localhost:8082" +} diff --git a/pype/settings/defaults/system_settings/environments/ftrack.json b/pype/settings/defaults/system_settings/environments/ftrack.json new file mode 100644 index 0000000000..4f25de027b --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/ftrack.json @@ -0,0 +1,18 @@ +{ + "FTRACK_SERVER": "https://pype.ftrackapp.com", + "FTRACK_ACTIONS_PATH": [ + "{PYPE_MODULE_ROOT}/pype/modules/ftrack/actions" + ], + "FTRACK_EVENTS_PATH": [ + "{PYPE_MODULE_ROOT}/pype/modules/ftrack/events" + ], + "PYTHONPATH": [ + "{PYPE_MODULE_ROOT}/pype/vendor", + "{PYTHONPATH}" + ], + "PYBLISHPLUGINPATH": [ + "{PYPE_MODULE_ROOT}/pype/plugins/ftrack/publish" + ], + "FTRACK_EVENTS_MONGO_DB": "pype", + "FTRACK_EVENTS_MONGO_COL": "ftrack_events" +} diff --git a/pype/settings/defaults/system_settings/environments/global.json b/pype/settings/defaults/system_settings/environments/global.json new file mode 100644 index 0000000000..ef528e6857 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/global.json @@ -0,0 +1,44 @@ +{ + "PYPE_STUDIO_NAME": "Studio Name", + "PYPE_STUDIO_CODE": "stu", + "PYPE_APP_ROOT": "{PYPE_SETUP_PATH}/pypeapp", + "PYPE_MODULE_ROOT": "{PYPE_SETUP_PATH}/repos/pype", + "PYPE_PROJECT_PLUGINS": "", + "STUDIO_SOFT": "{PYP_SETUP_ROOT}/soft", + "FFMPEG_PATH": { + "windows": "{VIRTUAL_ENV}/localized/ffmpeg_exec/windows/bin;{PYPE_SETUP_PATH}/vendor/ffmpeg_exec/windows/bin", + "darwin": "{VIRTUAL_ENV}/localized/ffmpeg_exec/darwin/bin:{PYPE_SETUP_PATH}/vendor/ffmpeg_exec/darwin/bin", + "linux": "{VIRTUAL_ENV}/localized/ffmpeg_exec/linux:{PYPE_SETUP_PATH}/vendor/ffmpeg_exec/linux" + }, + "DJV_PATH": { + "windows": [ + "C:/Program Files/djv-1.1.0-Windows-64/bin/djv_view.exe", + "C:/Program Files/DJV/bin/djv_view.exe", + "{STUDIO_SOFT}/djv/windows/bin/djv_view.exe" + ], + "linux": [ + "usr/local/djv/djv_view", + "{STUDIO_SOFT}/djv/linux/bin/djv_view" + ], + "darwin": "Application/DJV.app/Contents/MacOS/DJV" + }, + "PATH": [ + "{PYPE_CONFIG}/launchers", + "{PYPE_APP_ROOT}", + "{FFMPEG_PATH}", + "{PATH}" + ], + "PYPE_OCIO_CONFIG": "{STUDIO_SOFT}/OpenColorIO-Configs", + "PYTHONPATH": { + "windows": "{VIRTUAL_ENV}/Lib/site-packages;{PYPE_MODULE_ROOT}/pype/tools;{PYTHONPATH}", + "linux": "{VIRTUAL_ENV}/lib/python{PYTHON_VERSION}/site-packages:{PYPE_MODULE_ROOT}/pype/tools:{PYTHONPATH}", + "darwin": "{VIRTUAL_ENV}/lib/python{PYTHON_VERSION}/site-packages:{PYPE_MODULE_ROOT}/pype/tools:{PYTHONPATH}" + }, + "PYPE_PROJECT_CONFIGS": "{PYPE_SETUP_PATH}/../studio-project-configs", + "PYPE_PYTHON_EXE": { + "windows": "{VIRTUAL_ENV}/Scripts/python.exe", + "linux": "{VIRTUAL_ENV}/Scripts/python", + "darwin": "{VIRTUAL_ENV}/bin/python" + }, + "PYBLISH_GUI": "pyblish_pype" +} diff --git a/pype/settings/defaults/system_settings/environments/harmony.json b/pype/settings/defaults/system_settings/environments/harmony.json new file mode 100644 index 0000000000..d394343935 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/harmony.json @@ -0,0 +1,4 @@ +{ + "AVALON_HARMONY_WORKFILES_ON_LAUNCH": "1", + "PYBLISH_GUI_ALWAYS_EXEC": "1" +} diff --git a/pype/settings/defaults/system_settings/environments/houdini.json b/pype/settings/defaults/system_settings/environments/houdini.json new file mode 100644 index 0000000000..95c7d19088 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/houdini.json @@ -0,0 +1,12 @@ +{ + "HOUDINI_PATH": { + "darwin": "{PYPE_MODULE_ROOT}/setup/houdini:&", + "linux": "{PYPE_MODULE_ROOT}/setup/houdini:&", + "windows": "{PYPE_MODULE_ROOT}/setup/houdini;&" + }, + "HOUDINI_MENU_PATH": { + "darwin": "{PYPE_MODULE_ROOT}/setup/houdini:&", + "linux": "{PYPE_MODULE_ROOT}/setup/houdini:&", + "windows": "{PYPE_MODULE_ROOT}/setup/houdini;&" + } +} diff --git a/pype/settings/defaults/system_settings/environments/maya.json b/pype/settings/defaults/system_settings/environments/maya.json new file mode 100644 index 0000000000..7785b108f7 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/maya.json @@ -0,0 +1,14 @@ +{ + "PYTHONPATH": [ + "{PYPE_SETUP_PATH}/repos/avalon-core/setup/maya", + "{PYPE_SETUP_PATH}/repos/maya-look-assigner", + "{PYTHON_ENV}/python2/Lib/site-packages", + "{PYTHONPATH}" + ], + "MAYA_DISABLE_CLIC_IPM": "Yes", + "MAYA_DISABLE_CIP": "Yes", + "MAYA_DISABLE_CER": "Yes", + "PYMEL_SKIP_MEL_INIT": "Yes", + "LC_ALL": "C", + "PYPE_LOG_NO_COLORS": "Yes" +} diff --git a/pype/settings/defaults/system_settings/environments/maya_2018.json b/pype/settings/defaults/system_settings/environments/maya_2018.json new file mode 100644 index 0000000000..72a0c57ce3 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/maya_2018.json @@ -0,0 +1,11 @@ +{ + "MAYA_VERSION": "2018", + "MAYA_LOCATION": { + "darwin": "/Applications/Autodesk/maya{MAYA_VERSION}/Maya.app/Contents", + "linux": "/usr/autodesk/maya{MAYA_VERSION}", + "windows": "C:/Program Files/Autodesk/Maya{MAYA_VERSION}" + }, + "DYLD_LIBRARY_PATH": { + "darwin": "{MAYA_LOCATION}/MacOS" + } +} diff --git a/pype/settings/defaults/system_settings/environments/maya_2020.json b/pype/settings/defaults/system_settings/environments/maya_2020.json new file mode 100644 index 0000000000..efd0250bc8 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/maya_2020.json @@ -0,0 +1,11 @@ +{ + "MAYA_VERSION": "2020", + "MAYA_LOCATION": { + "darwin": "/Applications/Autodesk/maya{MAYA_VERSION}/Maya.app/Contents", + "linux": "/usr/autodesk/maya{MAYA_VERSION}", + "windows": "C:/Program Files/Autodesk/Maya{MAYA_VERSION}" + }, + "DYLD_LIBRARY_PATH": { + "darwin": "{MAYA_LOCATION}/MacOS" + } +} diff --git a/pype/settings/defaults/system_settings/environments/mayabatch.json b/pype/settings/defaults/system_settings/environments/mayabatch.json new file mode 100644 index 0000000000..7785b108f7 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/mayabatch.json @@ -0,0 +1,14 @@ +{ + "PYTHONPATH": [ + "{PYPE_SETUP_PATH}/repos/avalon-core/setup/maya", + "{PYPE_SETUP_PATH}/repos/maya-look-assigner", + "{PYTHON_ENV}/python2/Lib/site-packages", + "{PYTHONPATH}" + ], + "MAYA_DISABLE_CLIC_IPM": "Yes", + "MAYA_DISABLE_CIP": "Yes", + "MAYA_DISABLE_CER": "Yes", + "PYMEL_SKIP_MEL_INIT": "Yes", + "LC_ALL": "C", + "PYPE_LOG_NO_COLORS": "Yes" +} diff --git a/pype/settings/defaults/system_settings/environments/mayabatch_2019.json b/pype/settings/defaults/system_settings/environments/mayabatch_2019.json new file mode 100644 index 0000000000..aa7360a943 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/mayabatch_2019.json @@ -0,0 +1,11 @@ +{ + "MAYA_VERSION": "2019", + "MAYA_LOCATION": { + "darwin": "/Applications/Autodesk/maya{MAYA_VERSION}/Maya.app/Contents", + "linux": "/usr/autodesk/maya{MAYA_VERSION}", + "windows": "C:/Program Files/Autodesk/Maya{MAYA_VERSION}" + }, + "DYLD_LIBRARY_PATH": { + "darwin": "{MAYA_LOCATION}/MacOS" + } +} diff --git a/pype/settings/defaults/system_settings/environments/mtoa_3.1.1.json b/pype/settings/defaults/system_settings/environments/mtoa_3.1.1.json new file mode 100644 index 0000000000..f7b9f94d4e --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/mtoa_3.1.1.json @@ -0,0 +1,23 @@ +{ + "MTOA": "{PYPE_STUDIO_SOFTWARE}/arnold/mtoa_{MAYA_VERSION}_{MTOA_VERSION}", + "MTOA_VERSION": "3.1.1", + "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" + } +} diff --git a/pype/settings/defaults/system_settings/environments/muster.json b/pype/settings/defaults/system_settings/environments/muster.json new file mode 100644 index 0000000000..26f311146a --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/muster.json @@ -0,0 +1,3 @@ +{ + "MUSTER_REST_URL": "http://127.0.0.1:9890" +} diff --git a/pype/settings/defaults/system_settings/environments/nuke.json b/pype/settings/defaults/system_settings/environments/nuke.json new file mode 100644 index 0000000000..50dd31ac91 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/nuke.json @@ -0,0 +1,15 @@ +{ + "NUKE_PATH": [ + "{PYPE_SETUP_PATH}/repos/avalon-core/setup/nuke/nuke_path", + "{PYPE_MODULE_ROOT}/setup/nuke/nuke_path", + "{PYPE_STUDIO_PLUGINS}/nuke" + ], + "PATH": { + "windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}" + }, + "PYPE_LOG_NO_COLORS": "True", + "PYTHONPATH": { + "windows": "{VIRTUAL_ENV}/Lib/site-packages;{PYTHONPATH}", + "linux": "{VIRTUAL_ENV}/lib/python3.6/site-packages:{PYTHONPATH}" + } +} diff --git a/pype/settings/defaults/system_settings/environments/nukestudio.json b/pype/settings/defaults/system_settings/environments/nukestudio.json new file mode 100644 index 0000000000..b05e2411f0 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/nukestudio.json @@ -0,0 +1,11 @@ +{ + "HIERO_PLUGIN_PATH": [ + "{PYPE_MODULE_ROOT}/setup/nukestudio/hiero_plugin_path" + ], + "PATH": { + "windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}" + }, + "WORKFILES_STARTUP": "0", + "TAG_ASSETBUILD_STARTUP": "0", + "PYPE_LOG_NO_COLORS": "True" +} diff --git a/pype/settings/defaults/system_settings/environments/nukestudio_10.0.json b/pype/settings/defaults/system_settings/environments/nukestudio_10.0.json new file mode 100644 index 0000000000..9bdcef53c9 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/nukestudio_10.0.json @@ -0,0 +1,4 @@ +{ + "PYPE_LOG_NO_COLORS": "Yes", + "QT_PREFERRED_BINDING": "PySide" +} \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/environments/nukex.json b/pype/settings/defaults/system_settings/environments/nukex.json new file mode 100644 index 0000000000..2b77f44076 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/nukex.json @@ -0,0 +1,10 @@ +{ + "NUKE_PATH": [ + "{PYPE_SETUP_PATH}/repos/avalon-core/setup/nuke/nuke_path", + "{PYPE_MODULE_ROOT}/setup/nuke/nuke_path", + "{PYPE_STUDIO_PLUGINS}/nuke" + ], + "PATH": { + "windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}" + } +} diff --git a/pype/settings/defaults/system_settings/environments/nukex_10.0.json b/pype/settings/defaults/system_settings/environments/nukex_10.0.json new file mode 100644 index 0000000000..9bdcef53c9 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/nukex_10.0.json @@ -0,0 +1,4 @@ +{ + "PYPE_LOG_NO_COLORS": "Yes", + "QT_PREFERRED_BINDING": "PySide" +} \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/environments/photoshop.json b/pype/settings/defaults/system_settings/environments/photoshop.json new file mode 100644 index 0000000000..2208a88665 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/photoshop.json @@ -0,0 +1,4 @@ +{ + "AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH": "1", + "PYTHONPATH": "{PYTHONPATH}" +} diff --git a/pype/settings/defaults/system_settings/environments/premiere.json b/pype/settings/defaults/system_settings/environments/premiere.json new file mode 100644 index 0000000000..27dc5c564b --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/premiere.json @@ -0,0 +1,11 @@ +{ + "EXTENSIONS_PATH": { + "windows": "{USERPROFILE}/AppData/Roaming/Adobe/CEP/extensions", + "darvin": "{USER}/Library/Application Support/Adobe/CEP/extensions" + }, + "EXTENSIONS_CACHE_PATH": { + "windows": "{USERPROFILE}/AppData/Local/Temp/cep_cache", + "darvin": "{USER}/Library/Application Support/Adobe/CEP/cep_cache" + }, + "installed_zxp": "" +} diff --git a/pype/settings/defaults/system_settings/environments/resolve.json b/pype/settings/defaults/system_settings/environments/resolve.json new file mode 100644 index 0000000000..1ff197dd5a --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/resolve.json @@ -0,0 +1,40 @@ +{ + "RESOLVE_UTILITY_SCRIPTS_SOURCE_DIR": [ + "{STUDIO_SOFT}/davinci_resolve/scripts/python" + ], + "RESOLVE_SCRIPT_API": { + "windows": "{PROGRAMDATA}/Blackmagic Design/DaVinci Resolve/Support/Developer/Scripting", + "darvin": "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Developer/Scripting", + "linux": "/opt/resolve/Developer/Scripting" + }, + "RESOLVE_SCRIPT_LIB": { + "windows": "C:/Program Files/Blackmagic Design/DaVinci Resolve/fusionscript.dll", + "darvin": "/Applications/DaVinci Resolve/DaVinci Resolve.app/Contents/Libraries/Fusion/fusionscript.so", + "linux": "/opt/resolve/libs/Fusion/fusionscript.so" + }, + "RESOLVE_UTILITY_SCRIPTS_DIR": { + "windows": "{PROGRAMDATA}/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Comp", + "darvin": "/Library/Application Support/Blackmagic Design/DaVinci Resolve/Fusion/Scripts/Comp", + "linux": "/opt/resolve/Fusion/Scripts/Comp" + }, + "PYTHON36_RESOLVE": { + "windows": "{LOCALAPPDATA}/Programs/Python/Python36", + "darvin": "~/Library/Python/3.6/bin", + "linux": "/opt/Python/3.6/bin" + }, + "PYTHONPATH": [ + "{PYTHON36_RESOLVE}/Lib/site-packages", + "{VIRTUAL_ENV}/Lib/site-packages", + "{PYTHONPATH}", + "{RESOLVE_SCRIPT_API}/Modules", + "{PYTHONPATH}" + ], + "PATH": [ + "{PYTHON36_RESOLVE}", + "{PYTHON36_RESOLVE}/Scripts", + "{PATH}" + ], + "PRE_PYTHON_SCRIPT": "{PYPE_MODULE_ROOT}/pype/resolve/preload_console.py", + "PYPE_LOG_NO_COLORS": "True", + "RESOLVE_DEV": "True" +} diff --git a/pype/settings/defaults/system_settings/environments/storyboardpro.json b/pype/settings/defaults/system_settings/environments/storyboardpro.json new file mode 100644 index 0000000000..581ad4db45 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/storyboardpro.json @@ -0,0 +1,4 @@ +{ + "AVALON_TOONBOOM_WORKFILES_ON_LAUNCH": "1", + "PYBLISH_LITE_ALWAYS_EXEC": "1" +} diff --git a/pype/settings/defaults/system_settings/environments/unreal_4.24.json b/pype/settings/defaults/system_settings/environments/unreal_4.24.json new file mode 100644 index 0000000000..8feeb0230f --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/unreal_4.24.json @@ -0,0 +1,5 @@ +{ + "AVALON_UNREAL_PLUGIN": "{PYPE_SETUP_PATH}/repos/avalon-unreal-integration", + "PYPE_LOG_NO_COLORS": "True", + "QT_PREFERRED_BINDING": "PySide" +} diff --git a/pype/settings/defaults/system_settings/environments/vray_4300.json b/pype/settings/defaults/system_settings/environments/vray_4300.json new file mode 100644 index 0000000000..3212188441 --- /dev/null +++ b/pype/settings/defaults/system_settings/environments/vray_4300.json @@ -0,0 +1,15 @@ +{ + "VRAY_VERSION": "43001", + "VRAY_ROOT": "C:/vray/vray_{VRAY_VERSION}", + "MAYA_RENDER_DESC_PATH": "{VRAY_ROOT}/maya_root/bin/rendererDesc", + "VRAY_FOR_MAYA2019_MAIN": "{VRAY_ROOT}/maya_vray", + "VRAY_FOR_MAYA2019_PLUGINS": "{VRAY_ROOT}/maya_vray/vrayplugins", + "VRAY_PLUGINS": "{VRAY_ROOT}/maya_vray/vrayplugins", + "VRAY_OSL_PATH_MAYA2019": "{VRAY_ROOT}/vray/opensl", + "PATH": "{VRAY_ROOT}/maya_root/bin;{PATH}", + "MAYA_PLUG_IN_PATH": "{VRAY_ROOT}/maya_vray/plug-ins", + "MAYA_SCRIPT_PATH": "{VRAY_ROOT}/maya_vray/scripts", + "PYTHONPATH": "{VRAY_ROOT}/maya_vray/scripts;{PYTHONPATH}", + "XBMLANGPATH": "{VRAY_ROOT}/maya_vray/icons;{XBMLANGPATH}", + "VRAY_AUTH_CLIENT_FILE_PATH": "{VRAY_ROOT}" +} diff --git a/pype/settings/defaults/system_settings/global/applications.json b/pype/settings/defaults/system_settings/global/applications.json new file mode 100644 index 0000000000..3a74a85468 --- /dev/null +++ b/pype/settings/defaults/system_settings/global/applications.json @@ -0,0 +1,34 @@ +{ + "blender_2.80": true, + "blender_2.81": true, + "blender_2.82": true, + "blender_2.83": true, + "celaction_local": true, + "celaction_remote": true, + "harmony_17": true, + "maya_2017": true, + "maya_2018": true, + "maya_2019": true, + "maya_2020": true, + "nuke_10.0": true, + "nuke_11.2": true, + "nuke_11.3": true, + "nuke_12.0": true, + "nukex_10.0": true, + "nukex_11.2": true, + "nukex_11.3": true, + "nukex_12.0": true, + "nukestudio_10.0": true, + "nukestudio_11.2": true, + "nukestudio_11.3": true, + "nukestudio_12.0": true, + "houdini_16": true, + "houdini_16.5": false, + "houdini_17": true, + "houdini_18": true, + "premiere_2019": true, + "premiere_2020": true, + "resolve_16": true, + "storyboardpro_7": true, + "unreal_4.24": true +} \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/global/intent.json b/pype/settings/defaults/system_settings/global/intent.json new file mode 100644 index 0000000000..844bd1b518 --- /dev/null +++ b/pype/settings/defaults/system_settings/global/intent.json @@ -0,0 +1,8 @@ +{ + "items": { + "wip": "WIP", + "test": "TEST", + "final": "FINAL" + }, + "default": "wip" +} \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/global/tools.json b/pype/settings/defaults/system_settings/global/tools.json new file mode 100644 index 0000000000..93895c0e81 --- /dev/null +++ b/pype/settings/defaults/system_settings/global/tools.json @@ -0,0 +1,6 @@ +{ + "mtoa_3.0.1": true, + "mtoa_3.1.1": true, + "mtoa_3.2.0": true, + "yeti_2.1.2": true +} \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/global/tray_modules.json b/pype/settings/defaults/system_settings/global/tray_modules.json new file mode 100644 index 0000000000..0ff5b15552 --- /dev/null +++ b/pype/settings/defaults/system_settings/global/tray_modules.json @@ -0,0 +1,28 @@ +{ + "item_usage": { + "User settings": false, + "Ftrack": true, + "Muster": false, + "Avalon": true, + "Clockify": false, + "Standalone Publish": true, + "Logging": true, + "Idle Manager": true, + "Timers Manager": true, + "Rest Api": true, + "Adobe Communicator": true + }, + "attributes": { + "Rest Api": { + "default_port": 8021, + "exclude_ports": [] + }, + "Timers Manager": { + "full_time": 15.0, + "message_time": 0.5 + }, + "Clockify": { + "workspace_name": "" + } + } +} \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/launchers/blender_2.80.toml b/pype/settings/defaults/system_settings/launchers/blender_2.80.toml new file mode 100644 index 0000000000..5fea78b7b0 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/blender_2.80.toml @@ -0,0 +1,7 @@ +application_dir = "blender" +executable = "blender_2.80" +schema = "avalon-core:application-1.0" +label = "Blender 2.80" +ftrack_label = "Blender" +icon ="blender" +ftrack_icon = '{}/app_icons/blender.png' diff --git a/pype/settings/defaults/system_settings/launchers/blender_2.81.toml b/pype/settings/defaults/system_settings/launchers/blender_2.81.toml new file mode 100644 index 0000000000..4f85ee5558 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/blender_2.81.toml @@ -0,0 +1,7 @@ +application_dir = "blender" +executable = "blender_2.81" +schema = "avalon-core:application-1.0" +label = "Blender 2.81" +ftrack_label = "Blender" +icon ="blender" +ftrack_icon = '{}/app_icons/blender.png' diff --git a/pype/settings/defaults/system_settings/launchers/blender_2.82.toml b/pype/settings/defaults/system_settings/launchers/blender_2.82.toml new file mode 100644 index 0000000000..840001452e --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/blender_2.82.toml @@ -0,0 +1,7 @@ +application_dir = "blender" +executable = "blender_2.82" +schema = "avalon-core:application-1.0" +label = "Blender 2.82" +ftrack_label = "Blender" +icon ="blender" +ftrack_icon = '{}/app_icons/blender.png' diff --git a/pype/settings/defaults/system_settings/launchers/blender_2.83.toml b/pype/settings/defaults/system_settings/launchers/blender_2.83.toml new file mode 100644 index 0000000000..7fc8bf87b9 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/blender_2.83.toml @@ -0,0 +1,7 @@ +application_dir = "blender" +executable = "blender_2.83" +schema = "avalon-core:application-1.0" +label = "Blender 2.83" +ftrack_label = "Blender" +icon ="blender" +ftrack_icon = '{}/app_icons/blender.png' diff --git a/pype/settings/defaults/system_settings/launchers/celaction_local.toml b/pype/settings/defaults/system_settings/launchers/celaction_local.toml new file mode 100644 index 0000000000..aef3548e08 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/celaction_local.toml @@ -0,0 +1,8 @@ +executable = "celaction_local" +schema = "avalon-core:application-1.0" +application_dir = "celaction" +label = "CelAction2D" +ftrack_label = "CelAction2D" +icon ="celaction_local" +launch_hook = "pype/hooks/celaction/prelaunch.py/CelactionPrelaunchHook" +ftrack_icon = '{}/app_icons/celaction_local.png' diff --git a/pype/settings/defaults/system_settings/launchers/celaction_publish.toml b/pype/settings/defaults/system_settings/launchers/celaction_publish.toml new file mode 100644 index 0000000000..86f4ae39e7 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/celaction_publish.toml @@ -0,0 +1,7 @@ +schema = "avalon-core:application-1.0" +application_dir = "shell" +executable = "celaction_publish" +label = "Shell" + +[environment] +CREATE_NEW_CONSOLE = "Yes" diff --git a/pype/settings/defaults/system_settings/launchers/darwin/blender_2.82 b/pype/settings/defaults/system_settings/launchers/darwin/blender_2.82 new file mode 100644 index 0000000000..8254411ea2 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/darwin/blender_2.82 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +open -a blender $@ diff --git a/pype/settings/defaults/system_settings/launchers/darwin/harmony_17 b/pype/settings/defaults/system_settings/launchers/darwin/harmony_17 new file mode 100644 index 0000000000..b7eba2c2d0 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/darwin/harmony_17 @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +DIRNAME="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +set >~/environment.tmp +if [ $? -ne -0 ] ; then + echo "ERROR: cannot write to '~/environment.tmp'!" + read -n 1 -s -r -p "Press any key to exit" + return +fi +open -a Terminal.app "$DIRNAME/harmony_17_launch" diff --git a/pype/settings/defaults/system_settings/launchers/darwin/harmony_17_launch b/pype/settings/defaults/system_settings/launchers/darwin/harmony_17_launch new file mode 100644 index 0000000000..5dcf5db57e --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/darwin/harmony_17_launch @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +source ~/environment.tmp +export $(cut -d= -f1 ~/environment.tmp) +exe="/Applications/Toon Boom Harmony 17 Premium/Harmony Premium.app/Contents/MacOS/Harmony Premium" +$PYPE_PYTHON_EXE -c "import avalon.harmony;avalon.harmony.launch('$exe')" diff --git a/pype/settings/defaults/system_settings/launchers/darwin/python3 b/pype/settings/defaults/system_settings/launchers/darwin/python3 new file mode 100644 index 0000000000..c2b82c7638 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/darwin/python3 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +open /usr/bin/python3 --args $@ diff --git a/pype/settings/defaults/system_settings/launchers/harmony_17.toml b/pype/settings/defaults/system_settings/launchers/harmony_17.toml new file mode 100644 index 0000000000..dbb76444a7 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/harmony_17.toml @@ -0,0 +1,8 @@ +application_dir = "harmony" +label = "Harmony 17" +ftrack_label = "Harmony" +schema = "avalon-core:application-1.0" +executable = "harmony_17" +description = "" +icon ="harmony_icon" +ftrack_icon = '{}/app_icons/harmony.png' diff --git a/pype/settings/defaults/system_settings/launchers/houdini_16.toml b/pype/settings/defaults/system_settings/launchers/houdini_16.toml new file mode 100644 index 0000000000..e29fa74cad --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/houdini_16.toml @@ -0,0 +1,7 @@ +executable = "houdini_16" +schema = "avalon-core:application-1.0" +application_dir = "houdini" +label = "Houdini 16" +ftrack_label = "Houdini" +icon = "houdini_icon" +ftrack_icon = '{}/app_icons/houdini.png' diff --git a/pype/settings/defaults/system_settings/launchers/houdini_17.toml b/pype/settings/defaults/system_settings/launchers/houdini_17.toml new file mode 100644 index 0000000000..5d01364330 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/houdini_17.toml @@ -0,0 +1,7 @@ +executable = "houdini_17" +schema = "avalon-core:application-1.0" +application_dir = "houdini" +label = "Houdini 17.0" +ftrack_label = "Houdini" +icon = "houdini_icon" +ftrack_icon = '{}/app_icons/houdini.png' diff --git a/pype/settings/defaults/system_settings/launchers/houdini_18.toml b/pype/settings/defaults/system_settings/launchers/houdini_18.toml new file mode 100644 index 0000000000..93b9a3334d --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/houdini_18.toml @@ -0,0 +1,7 @@ +executable = "houdini_18" +schema = "avalon-core:application-1.0" +application_dir = "houdini" +label = "Houdini 18" +ftrack_label = "Houdini" +icon = "houdini_icon" +ftrack_icon = '{}/app_icons/houdini.png' diff --git a/pype/settings/defaults/system_settings/launchers/linux/maya2016 b/pype/settings/defaults/system_settings/launchers/linux/maya2016 new file mode 100644 index 0000000000..98424304b1 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/linux/maya2016 @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +maya_path = "/usr/autodesk/maya2016/bin/maya" + +if [[ -z $PYPE_LOG_NO_COLORS ]]; then + $maya_path -file "$AVALON_LAST_WORKFILE" $@ +else + $maya_path $@ diff --git a/pype/settings/defaults/system_settings/launchers/linux/maya2017 b/pype/settings/defaults/system_settings/launchers/linux/maya2017 new file mode 100644 index 0000000000..7a2662a55e --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/linux/maya2017 @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +maya_path = "/usr/autodesk/maya2017/bin/maya" + +if [[ -z $AVALON_LAST_WORKFILE ]]; then + $maya_path -file "$AVALON_LAST_WORKFILE" $@ +else + $maya_path $@ diff --git a/pype/settings/defaults/system_settings/launchers/linux/maya2018 b/pype/settings/defaults/system_settings/launchers/linux/maya2018 new file mode 100644 index 0000000000..db832b3fe7 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/linux/maya2018 @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +maya_path = "/usr/autodesk/maya2018/bin/maya" + +if [[ -z $AVALON_LAST_WORKFILE ]]; then + $maya_path -file "$AVALON_LAST_WORKFILE" $@ +else + $maya_path $@ diff --git a/pype/settings/defaults/system_settings/launchers/linux/maya2019 b/pype/settings/defaults/system_settings/launchers/linux/maya2019 new file mode 100644 index 0000000000..8398734ab9 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/linux/maya2019 @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +maya_path = "/usr/autodesk/maya2019/bin/maya" + +if [[ -z $AVALON_LAST_WORKFILE ]]; then + $maya_path -file "$AVALON_LAST_WORKFILE" $@ +else + $maya_path $@ diff --git a/pype/settings/defaults/system_settings/launchers/linux/maya2020 b/pype/settings/defaults/system_settings/launchers/linux/maya2020 new file mode 100644 index 0000000000..18a1edd598 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/linux/maya2020 @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +maya_path = "/usr/autodesk/maya2020/bin/maya" + +if [[ -z $AVALON_LAST_WORKFILE ]]; then + $maya_path -file "$AVALON_LAST_WORKFILE" $@ +else + $maya_path $@ diff --git a/pype/settings/defaults/system_settings/launchers/linux/nuke11.3 b/pype/settings/defaults/system_settings/launchers/linux/nuke11.3 new file mode 100644 index 0000000000..b1c9a90d74 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/linux/nuke11.3 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke11.3v5/Nuke11.3' diff --git a/pype/settings/defaults/system_settings/launchers/linux/nuke12.0 b/pype/settings/defaults/system_settings/launchers/linux/nuke12.0 new file mode 100644 index 0000000000..99ea1a6b0c --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/linux/nuke12.0 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke12.0v1/Nuke12.0' diff --git a/pype/settings/defaults/system_settings/launchers/linux/nukestudio11.3 b/pype/settings/defaults/system_settings/launchers/linux/nukestudio11.3 new file mode 100644 index 0000000000..750d54a7d5 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/linux/nukestudio11.3 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke11.3v5/Nuke11.3 --studio' diff --git a/pype/settings/defaults/system_settings/launchers/linux/nukestudio12.0 b/pype/settings/defaults/system_settings/launchers/linux/nukestudio12.0 new file mode 100644 index 0000000000..ba5cf654a8 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/linux/nukestudio12.0 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke12.0v1/Nuke12.0 --studio' diff --git a/pype/settings/defaults/system_settings/launchers/linux/nukex11.3 b/pype/settings/defaults/system_settings/launchers/linux/nukex11.3 new file mode 100644 index 0000000000..d913e4b961 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/linux/nukex11.3 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke11.3v5/Nuke11.3 -nukex' diff --git a/pype/settings/defaults/system_settings/launchers/linux/nukex12.0 b/pype/settings/defaults/system_settings/launchers/linux/nukex12.0 new file mode 100644 index 0000000000..da2721c48b --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/linux/nukex12.0 @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +gnome-terminal -e '/usr/local/Nuke12.0v1/Nuke12.0 -nukex' diff --git a/pype/settings/defaults/system_settings/launchers/maya_2016.toml b/pype/settings/defaults/system_settings/launchers/maya_2016.toml new file mode 100644 index 0000000000..d69c4effaf --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/maya_2016.toml @@ -0,0 +1,26 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2016x64" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2016" +description = "" +icon ="maya_icon" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" + +[environment] +MAYA_DISABLE_CLIC_IPM = "Yes" # Disable the AdSSO process +MAYA_DISABLE_CIP = "Yes" # Shorten time to boot +MAYA_DISABLE_CER = "Yes" +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/system_settings/launchers/maya_2017.toml b/pype/settings/defaults/system_settings/launchers/maya_2017.toml new file mode 100644 index 0000000000..2d1c35b530 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/maya_2017.toml @@ -0,0 +1,28 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2017" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2017" +description = "" +icon ="maya_icon" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" + +[environment] +MAYA_DISABLE_CLIC_IPM = "Yes" # Disable the AdSSO process +MAYA_DISABLE_CIP = "Yes" # Shorten time to boot +MAYA_DISABLE_CER = "Yes" +PYMEL_SKIP_MEL_INIT = "Yes" +LC_ALL= "C" # Mute color management warnings +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/system_settings/launchers/maya_2018.toml b/pype/settings/defaults/system_settings/launchers/maya_2018.toml new file mode 100644 index 0000000000..f180263fa2 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/maya_2018.toml @@ -0,0 +1,14 @@ +application_dir = "maya" +default_dirs = [ + "renders" +] +label = "Autodesk Maya 2018" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2018" +description = "" +icon ="maya_icon" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" diff --git a/pype/settings/defaults/system_settings/launchers/maya_2019.toml b/pype/settings/defaults/system_settings/launchers/maya_2019.toml new file mode 100644 index 0000000000..7ec2cbcedd --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/maya_2019.toml @@ -0,0 +1,14 @@ +application_dir = "maya" +default_dirs = [ + "renders" +] +label = "Autodesk Maya 2019" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2019" +description = "" +icon ="maya_icon" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" diff --git a/pype/settings/defaults/system_settings/launchers/maya_2020.toml b/pype/settings/defaults/system_settings/launchers/maya_2020.toml new file mode 100644 index 0000000000..49d84ef9a0 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/maya_2020.toml @@ -0,0 +1,14 @@ +application_dir = "maya" +default_dirs = [ + "renders" +] +label = "Autodesk Maya 2020" +ftrack_label = "Maya" +schema = "avalon-core:application-1.0" +executable = "maya2020" +description = "" +icon ="maya_icon" +ftrack_icon = '{}/app_icons/maya.png' + +[copy] +"{PYPE_MODULE_ROOT}/pype/resources/maya/workspace.mel" = "workspace.mel" diff --git a/pype/settings/defaults/system_settings/launchers/mayabatch_2019.toml b/pype/settings/defaults/system_settings/launchers/mayabatch_2019.toml new file mode 100644 index 0000000000..a928618d2b --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/mayabatch_2019.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2019x64" +schema = "avalon-core:application-1.0" +executable = "mayabatch2019" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/system_settings/launchers/mayabatch_2020.toml b/pype/settings/defaults/system_settings/launchers/mayabatch_2020.toml new file mode 100644 index 0000000000..cd1e1e4474 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/mayabatch_2020.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2020x64" +schema = "avalon-core:application-1.0" +executable = "mayabatch2020" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/system_settings/launchers/mayapy2016.toml b/pype/settings/defaults/system_settings/launchers/mayapy2016.toml new file mode 100644 index 0000000000..ad1e3dee86 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/mayapy2016.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2016x64" +schema = "avalon-core:application-1.0" +executable = "mayapy2016" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/system_settings/launchers/mayapy2017.toml b/pype/settings/defaults/system_settings/launchers/mayapy2017.toml new file mode 100644 index 0000000000..8d2095ff47 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/mayapy2017.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2017x64" +schema = "avalon-core:application-1.0" +executable = "mayapy2017" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/system_settings/launchers/mayapy2018.toml b/pype/settings/defaults/system_settings/launchers/mayapy2018.toml new file mode 100644 index 0000000000..597744fd85 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/mayapy2018.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2018x64" +schema = "avalon-core:application-1.0" +executable = "mayapy2017" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/system_settings/launchers/mayapy2019.toml b/pype/settings/defaults/system_settings/launchers/mayapy2019.toml new file mode 100644 index 0000000000..3c8a9860f9 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/mayapy2019.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2019x64" +schema = "avalon-core:application-1.0" +executable = "mayapy2019" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/system_settings/launchers/mayapy2020.toml b/pype/settings/defaults/system_settings/launchers/mayapy2020.toml new file mode 100644 index 0000000000..8f2d2e4a67 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/mayapy2020.toml @@ -0,0 +1,17 @@ +application_dir = "maya" +default_dirs = [ + "scenes", + "data", + "renderData/shaders", + "images" +] +label = "Autodesk Maya 2020x64" +schema = "avalon-core:application-1.0" +executable = "mayapy2020" +description = "" + +[environment] +PYTHONPATH = [ + "{AVALON_CORE}/setup/maya", + "{PYTHONPATH}" +] diff --git a/pype/settings/defaults/system_settings/launchers/myapp.toml b/pype/settings/defaults/system_settings/launchers/myapp.toml new file mode 100644 index 0000000000..21da0d52b2 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/myapp.toml @@ -0,0 +1,5 @@ +executable = "python" +schema = "avalon-core:application-1.0" +application_dir = "myapp" +label = "My App" +arguments = [ "-c", "import sys; from Qt import QtWidgets; if __name__ == '__main__':;\n app = QtWidgets.QApplication(sys.argv);\n window = QtWidgets.QWidget();\n window.setWindowTitle(\"My App\");\n window.resize(400, 300);\n window.show();\n app.exec_();\n",] \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/launchers/nuke_10.0.toml b/pype/settings/defaults/system_settings/launchers/nuke_10.0.toml new file mode 100644 index 0000000000..2195fd3e82 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/nuke_10.0.toml @@ -0,0 +1,7 @@ +executable = "nuke10.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke 10.0v4" +ftrack_label = "Nuke" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/system_settings/launchers/nuke_11.0.toml b/pype/settings/defaults/system_settings/launchers/nuke_11.0.toml new file mode 100644 index 0000000000..0c981b479a --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/nuke_11.0.toml @@ -0,0 +1,7 @@ +executable = "nuke11.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke 11.0" +ftrack_label = "Nuke" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/system_settings/launchers/nuke_11.2.toml b/pype/settings/defaults/system_settings/launchers/nuke_11.2.toml new file mode 100644 index 0000000000..57c962d126 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/nuke_11.2.toml @@ -0,0 +1,7 @@ +executable = "nuke11.2" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke 11.2" +ftrack_label = "Nuke" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/system_settings/launchers/nuke_11.3.toml b/pype/settings/defaults/system_settings/launchers/nuke_11.3.toml new file mode 100644 index 0000000000..87f769c23b --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/nuke_11.3.toml @@ -0,0 +1,7 @@ +executable = "nuke11.3" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke 11.3" +ftrack_label = "Nuke" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/system_settings/launchers/nuke_12.0.toml b/pype/settings/defaults/system_settings/launchers/nuke_12.0.toml new file mode 100644 index 0000000000..62936b4cdb --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/nuke_12.0.toml @@ -0,0 +1,7 @@ +executable = "nuke12.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "Nuke 12.0" +ftrack_label = "Nuke" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/system_settings/launchers/nukestudio_10.0.toml b/pype/settings/defaults/system_settings/launchers/nukestudio_10.0.toml new file mode 100644 index 0000000000..41601e4d40 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/nukestudio_10.0.toml @@ -0,0 +1,7 @@ +executable = "nukestudio10.0" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio 10.0" +ftrack_label = "NukeStudio" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/system_settings/launchers/nukestudio_11.0.toml b/pype/settings/defaults/system_settings/launchers/nukestudio_11.0.toml new file mode 100644 index 0000000000..7a9d84707a --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/nukestudio_11.0.toml @@ -0,0 +1,7 @@ +executable = "nukestudio11.0" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio 11.0" +ftrack_label = "NukeStudio" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/system_settings/launchers/nukestudio_11.2.toml b/pype/settings/defaults/system_settings/launchers/nukestudio_11.2.toml new file mode 100644 index 0000000000..21557033ca --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/nukestudio_11.2.toml @@ -0,0 +1,7 @@ +executable = "nukestudio11.2" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio 11.2" +ftrack_label = "NukeStudio" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/system_settings/launchers/nukestudio_11.3.toml b/pype/settings/defaults/system_settings/launchers/nukestudio_11.3.toml new file mode 100644 index 0000000000..1946ad6c3b --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/nukestudio_11.3.toml @@ -0,0 +1,7 @@ +executable = "nukestudio11.3" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio 11.3" +ftrack_label = "NukeStudio" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/system_settings/launchers/nukestudio_12.0.toml b/pype/settings/defaults/system_settings/launchers/nukestudio_12.0.toml new file mode 100644 index 0000000000..4ce7f9b538 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/nukestudio_12.0.toml @@ -0,0 +1,7 @@ +executable = "nukestudio12.0" +schema = "avalon-core:application-1.0" +application_dir = "nukestudio" +label = "NukeStudio 12.0" +ftrack_label = "NukeStudio" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nuke.png' diff --git a/pype/settings/defaults/system_settings/launchers/nukex_10.0.toml b/pype/settings/defaults/system_settings/launchers/nukex_10.0.toml new file mode 100644 index 0000000000..7dee22996d --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/nukex_10.0.toml @@ -0,0 +1,7 @@ +executable = "nukex10.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX 10.0" +ftrack_label = "NukeX" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/settings/defaults/system_settings/launchers/nukex_11.0.toml b/pype/settings/defaults/system_settings/launchers/nukex_11.0.toml new file mode 100644 index 0000000000..c2b4970a26 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/nukex_11.0.toml @@ -0,0 +1,7 @@ +executable = "nukex11.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX 11.2" +ftrack_label = "NukeX" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/settings/defaults/system_settings/launchers/nukex_11.2.toml b/pype/settings/defaults/system_settings/launchers/nukex_11.2.toml new file mode 100644 index 0000000000..3857b9995c --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/nukex_11.2.toml @@ -0,0 +1,7 @@ +executable = "nukex11.2" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX 11.2" +ftrack_label = "NukeX" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/settings/defaults/system_settings/launchers/nukex_11.3.toml b/pype/settings/defaults/system_settings/launchers/nukex_11.3.toml new file mode 100644 index 0000000000..56428470eb --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/nukex_11.3.toml @@ -0,0 +1,7 @@ +executable = "nukex11.3" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX 11.3" +ftrack_label = "NukeX" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/settings/defaults/system_settings/launchers/nukex_12.0.toml b/pype/settings/defaults/system_settings/launchers/nukex_12.0.toml new file mode 100644 index 0000000000..33d7fddb88 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/nukex_12.0.toml @@ -0,0 +1,7 @@ +executable = "nukex12.0" +schema = "avalon-core:application-1.0" +application_dir = "nuke" +label = "NukeX 12.0" +ftrack_label = "NukeX" +icon ="nuke_icon" +ftrack_icon = '{}/app_icons/nukex.png' diff --git a/pype/settings/defaults/system_settings/launchers/photoshop_2020.toml b/pype/settings/defaults/system_settings/launchers/photoshop_2020.toml new file mode 100644 index 0000000000..117b668232 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/photoshop_2020.toml @@ -0,0 +1,8 @@ +executable = "photoshop_2020" +schema = "avalon-core:application-1.0" +application_dir = "photoshop" +label = "Adobe Photoshop 2020" +icon ="photoshop_icon" +ftrack_label = "Photoshop" +ftrack_icon = '{}/app_icons/photoshop.png' +launch_hook = "pype/hooks/photoshop/prelaunch.py/PhotoshopPrelaunch" diff --git a/pype/settings/defaults/system_settings/launchers/premiere_2019.toml b/pype/settings/defaults/system_settings/launchers/premiere_2019.toml new file mode 100644 index 0000000000..f4c19c62cb --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/premiere_2019.toml @@ -0,0 +1,8 @@ +executable = "premiere_pro_2019" +schema = "avalon-core:application-1.0" +application_dir = "premiere" +label = "Adobe Premiere Pro CC 2019" +icon ="premiere_icon" + +ftrack_label = "Premiere" +ftrack_icon = '{}/app_icons/premiere.png' diff --git a/pype/settings/defaults/system_settings/launchers/premiere_2020.toml b/pype/settings/defaults/system_settings/launchers/premiere_2020.toml new file mode 100644 index 0000000000..4d721c898f --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/premiere_2020.toml @@ -0,0 +1,9 @@ +executable = "premiere_pro_2020" +schema = "avalon-core:application-1.0" +application_dir = "premiere" +label = "Adobe Premiere Pro CC 2020" +launch_hook = "pype/hooks/premiere/prelaunch.py/PremierePrelaunch" +icon ="premiere_icon" + +ftrack_label = "Premiere" +ftrack_icon = '{}/app_icons/premiere.png' diff --git a/pype/settings/defaults/system_settings/launchers/python_2.toml b/pype/settings/defaults/system_settings/launchers/python_2.toml new file mode 100644 index 0000000000..e9e8dd7899 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/python_2.toml @@ -0,0 +1,10 @@ +schema = "avalon-core:application-1.0" +application_dir = "python" +executable = "python" +label = "Python 2" +ftrack_label = "Python" +icon ="python_icon" +ftrack_icon = '{}/app_icons/python.png' + +[environment] +CREATE_NEW_CONSOLE = "Yes" diff --git a/pype/settings/defaults/system_settings/launchers/python_3.toml b/pype/settings/defaults/system_settings/launchers/python_3.toml new file mode 100644 index 0000000000..5cbd8b2943 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/python_3.toml @@ -0,0 +1,10 @@ +schema = "avalon-core:application-1.0" +application_dir = "python" +executable = "python3" +label = "Python 3" +ftrack_label = "Python" +icon ="python_icon" +ftrack_icon = '{}/app_icons/python.png' + +[environment] +CREATE_NEW_CONSOLE = "Yes" diff --git a/pype/settings/defaults/system_settings/launchers/resolve_16.toml b/pype/settings/defaults/system_settings/launchers/resolve_16.toml new file mode 100644 index 0000000000..430fd1a638 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/resolve_16.toml @@ -0,0 +1,9 @@ +executable = "resolve_16" +schema = "avalon-core:application-1.0" +application_dir = "resolve" +label = "BM DaVinci Resolve 16" +launch_hook = "pype/hooks/resolve/prelaunch.py/ResolvePrelaunch" +icon ="resolve" + +ftrack_label = "BM DaVinci Resolve" +ftrack_icon = '{}/app_icons/resolve.png' diff --git a/pype/settings/defaults/system_settings/launchers/shell.toml b/pype/settings/defaults/system_settings/launchers/shell.toml new file mode 100644 index 0000000000..959ad392ea --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/shell.toml @@ -0,0 +1,7 @@ +schema = "avalon-core:application-1.0" +application_dir = "shell" +executable = "shell" +label = "Shell" + +[environment] +CREATE_NEW_CONSOLE = "Yes" \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/launchers/storyboardpro_7.toml b/pype/settings/defaults/system_settings/launchers/storyboardpro_7.toml new file mode 100644 index 0000000000..ce8e96a49d --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/storyboardpro_7.toml @@ -0,0 +1,8 @@ +application_dir = "storyboardpro" +label = "Storyboard Pro 7" +ftrack_label = "Storyboard Pro" +schema = "avalon-core:application-1.0" +executable = "storyboardpro_7" +description = "" +icon ="storyboardpro_icon" +ftrack_icon = '{}/app_icons/storyboardpro.png' diff --git a/pype/settings/defaults/system_settings/launchers/unreal_4.24.toml b/pype/settings/defaults/system_settings/launchers/unreal_4.24.toml new file mode 100644 index 0000000000..0a799e5dcb --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/unreal_4.24.toml @@ -0,0 +1,8 @@ +executable = "unreal" +schema = "avalon-core:application-1.0" +application_dir = "unreal" +label = "Unreal Editor 4.24" +ftrack_label = "UnrealEditor" +icon ="ue4_icon" +launch_hook = "pype/hooks/unreal/unreal_prelaunch.py/UnrealPrelaunch" +ftrack_icon = '{}/app_icons/ue4.png' diff --git a/pype/settings/defaults/system_settings/launchers/windows/blender_2.80.bat b/pype/settings/defaults/system_settings/launchers/windows/blender_2.80.bat new file mode 100644 index 0000000000..5b8a37356b --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/blender_2.80.bat @@ -0,0 +1,11 @@ +set __app__="Blender" +set __exe__="C:\Program Files\Blender Foundation\Blender 2.80\blender.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/blender_2.81.bat b/pype/settings/defaults/system_settings/launchers/windows/blender_2.81.bat new file mode 100644 index 0000000000..a900b18eda --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/blender_2.81.bat @@ -0,0 +1,11 @@ +set __app__="Blender" +set __exe__="C:\Program Files\Blender Foundation\Blender 2.81\blender.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/blender_2.82.bat b/pype/settings/defaults/system_settings/launchers/windows/blender_2.82.bat new file mode 100644 index 0000000000..7105c1efe1 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/blender_2.82.bat @@ -0,0 +1,11 @@ +set __app__="Blender" +set __exe__="C:\Program Files\Blender Foundation\Blender 2.82\blender.exe" --python-use-system-env +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/blender_2.83.bat b/pype/settings/defaults/system_settings/launchers/windows/blender_2.83.bat new file mode 100644 index 0000000000..671952f0d7 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/blender_2.83.bat @@ -0,0 +1,11 @@ +set __app__="Blender" +set __exe__="C:\Program Files\Blender Foundation\Blender 2.83\blender.exe" --python-use-system-env +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/celaction_local.bat b/pype/settings/defaults/system_settings/launchers/windows/celaction_local.bat new file mode 100644 index 0000000000..8f2171617e --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/celaction_local.bat @@ -0,0 +1,19 @@ +set __app__="CelAction2D" +set __app_dir__="C:\Program Files (x86)\CelAction\" +set __exe__="C:\Program Files (x86)\CelAction\CelAction2D.exe" + +if not exist %__exe__% goto :missing_app + +pushd %__app_dir__% + +if "%PYPE_CELACTION_PROJECT_FILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% "%PYPE_CELACTION_PROJECT_FILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/celaction_publish.bat b/pype/settings/defaults/system_settings/launchers/windows/celaction_publish.bat new file mode 100644 index 0000000000..77ec2ac24e --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/celaction_publish.bat @@ -0,0 +1,3 @@ +echo %* + +%PYPE_PYTHON_EXE% "%PYPE_MODULE_ROOT%\pype\hosts\celaction\cli.py" %* diff --git a/pype/settings/defaults/system_settings/launchers/windows/harmony_17.bat b/pype/settings/defaults/system_settings/launchers/windows/harmony_17.bat new file mode 100644 index 0000000000..0822650875 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/harmony_17.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Harmony 17" +set __exe__="C:/Program Files (x86)/Toon Boom Animation/Toon Boom Harmony 17 Premium/win64/bin/HarmonyPremium.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% cmd.exe /k "python -c ^"import avalon.harmony;avalon.harmony.launch("%__exe__%")^"" + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/houdini_16.bat b/pype/settings/defaults/system_settings/launchers/windows/houdini_16.bat new file mode 100644 index 0000000000..018ba08b4c --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/houdini_16.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Houdini 16.0" +set __exe__="C:\Program Files\Side Effects Software\Houdini 16.0.621\bin\houdini.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/houdini_17.bat b/pype/settings/defaults/system_settings/launchers/windows/houdini_17.bat new file mode 100644 index 0000000000..950a599623 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/houdini_17.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Houdini 17.0" +set __exe__="C:\Program Files\Side Effects Software\Houdini 17.0.459\bin\houdini.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/houdini_18.bat b/pype/settings/defaults/system_settings/launchers/windows/houdini_18.bat new file mode 100644 index 0000000000..3d6b1ae258 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/houdini_18.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Houdini 18.0" +set __exe__="C:\Program Files\Side Effects Software\Houdini 18.0.287\bin\houdini.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/maya2016.bat b/pype/settings/defaults/system_settings/launchers/windows/maya2016.bat new file mode 100644 index 0000000000..54f15cf269 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/maya2016.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Maya 2016" +set __exe__="C:\Program Files\Autodesk\Maya2016\bin\maya.exe" +if not exist %__exe__% goto :missing_app + +if "%AVALON_LAST_WORKFILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% -file "%AVALON_LAST_WORKFILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/maya2017.bat b/pype/settings/defaults/system_settings/launchers/windows/maya2017.bat new file mode 100644 index 0000000000..5c2aeb495c --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/maya2017.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Maya 2017" +set __exe__="C:\Program Files\Autodesk\Maya2017\bin\maya.exe" +if not exist %__exe__% goto :missing_app + +if "%AVALON_LAST_WORKFILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% -file "%AVALON_LAST_WORKFILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/maya2018.bat b/pype/settings/defaults/system_settings/launchers/windows/maya2018.bat new file mode 100644 index 0000000000..28cf776c77 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/maya2018.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Maya 2018" +set __exe__="C:\Program Files\Autodesk\Maya2018\bin\maya.exe" +if not exist %__exe__% goto :missing_app + +if "%AVALON_LAST_WORKFILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% -file "%AVALON_LAST_WORKFILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/maya2019.bat b/pype/settings/defaults/system_settings/launchers/windows/maya2019.bat new file mode 100644 index 0000000000..7e80dd2557 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/maya2019.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Maya 2019" +set __exe__="C:\Program Files\Autodesk\Maya2019\bin\maya.exe" +if not exist %__exe__% goto :missing_app + +if "%AVALON_LAST_WORKFILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% -file "%AVALON_LAST_WORKFILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/maya2020.bat b/pype/settings/defaults/system_settings/launchers/windows/maya2020.bat new file mode 100644 index 0000000000..b2acb5df5a --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/maya2020.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Maya 2020" +set __exe__="C:\Program Files\Autodesk\maya2020\bin\maya.exe" +if not exist %__exe__% goto :missing_app + +if "%AVALON_LAST_WORKFILE%"=="" ( + start %__app__% %__exe__% %* +) else ( + start %__app__% %__exe__% -file "%AVALON_LAST_WORKFILE%" %* +) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/mayabatch2019.bat b/pype/settings/defaults/system_settings/launchers/windows/mayabatch2019.bat new file mode 100644 index 0000000000..ddd9b9b956 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/mayabatch2019.bat @@ -0,0 +1,14 @@ +@echo off + +set __app__="Maya Batch 2019" +set __exe__="C:\Program Files\Autodesk\Maya2019\bin\mayabatch.exe" +if not exist %__exe__% goto :missing_app + +echo "running maya : %*" +%__exe__% %* +echo "done." +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/mayabatch2020.bat b/pype/settings/defaults/system_settings/launchers/windows/mayabatch2020.bat new file mode 100644 index 0000000000..b1cbc6dbb6 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/mayabatch2020.bat @@ -0,0 +1,14 @@ +@echo off + +set __app__="Maya Batch 2020" +set __exe__="C:\Program Files\Autodesk\Maya2020\bin\mayabatch.exe" +if not exist %__exe__% goto :missing_app + +echo "running maya : %*" +%__exe__% %* +echo "done." +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/mayapy2016.bat b/pype/settings/defaults/system_settings/launchers/windows/mayapy2016.bat new file mode 100644 index 0000000000..205991fd3d --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/mayapy2016.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Mayapy 2016" +set __exe__="C:\Program Files\Autodesk\Maya2016\bin\mayapy.exe" +if not exist %__exe__% goto :missing_app + +call %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found at %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/mayapy2017.bat b/pype/settings/defaults/system_settings/launchers/windows/mayapy2017.bat new file mode 100644 index 0000000000..14aacc5a7f --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/mayapy2017.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Mayapy 2017" +set __exe__="C:\Program Files\Autodesk\Maya2017\bin\mayapy.exe" +if not exist %__exe__% goto :missing_app + +call %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found at %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/mayapy2018.bat b/pype/settings/defaults/system_settings/launchers/windows/mayapy2018.bat new file mode 100644 index 0000000000..c47c472f46 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/mayapy2018.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Mayapy 2018" +set __exe__="C:\Program Files\Autodesk\Maya2018\bin\mayapy.exe" +if not exist %__exe__% goto :missing_app + +call %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found at %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/mayapy2019.bat b/pype/settings/defaults/system_settings/launchers/windows/mayapy2019.bat new file mode 100644 index 0000000000..73ca5b2d40 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/mayapy2019.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Mayapy 2019" +set __exe__="C:\Program Files\Autodesk\Maya2019\bin\mayapy.exe" +if not exist %__exe__% goto :missing_app + +call %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found at %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/mayapy2020.bat b/pype/settings/defaults/system_settings/launchers/windows/mayapy2020.bat new file mode 100644 index 0000000000..770a03dcf5 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/mayapy2020.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Mayapy 2020" +set __exe__="C:\Program Files\Autodesk\Maya2020\bin\mayapy.exe" +if not exist %__exe__% goto :missing_app + +call %__exe__% %* + +goto :eofS + +:missing_app + echo ERROR: %__app__% not found at %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/nuke10.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nuke10.0.bat new file mode 100644 index 0000000000..a47cbdfb20 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/nuke10.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Nuke10.0v4" +set __exe__="C:\Program Files\Nuke10.0v4\Nuke10.0.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/nuke11.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nuke11.0.bat new file mode 100644 index 0000000000..a374c5cf5b --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/nuke11.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Nuke11.0v4" +set __exe__="C:\Program Files\Nuke11.0v4\Nuke11.0.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/nuke11.2.bat b/pype/settings/defaults/system_settings/launchers/windows/nuke11.2.bat new file mode 100644 index 0000000000..4c777ac28c --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/nuke11.2.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Nuke11.2v3" +set __exe__="C:\Program Files\Nuke11.2v3\Nuke11.2.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/nuke11.3.bat b/pype/settings/defaults/system_settings/launchers/windows/nuke11.3.bat new file mode 100644 index 0000000000..a023f5f46f --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/nuke11.3.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Nuke11.3v1" +set __exe__="C:\Program Files\Nuke11.3v1\Nuke11.3.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/nuke12.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nuke12.0.bat new file mode 100644 index 0000000000..d8fb5772bb --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/nuke12.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Nuke12.0v1" +set __exe__="C:\Program Files\Nuke12.0v1\Nuke12.0.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/nukestudio10.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nukestudio10.0.bat new file mode 100644 index 0000000000..82f833667c --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/nukestudio10.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeStudio10.0v4" +set __exe__="C:\Program Files\Nuke10.0v4\Nuke10.0.exe" --studio +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/nukestudio11.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nukestudio11.0.bat new file mode 100644 index 0000000000..b66797727e --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/nukestudio11.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeStudio11.0v4" +set __exe__="C:\Program Files\Nuke11.0v4\Nuke11.0.exe" -studio +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/nukestudio11.2.bat b/pype/settings/defaults/system_settings/launchers/windows/nukestudio11.2.bat new file mode 100644 index 0000000000..a653d816b4 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/nukestudio11.2.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeStudio11.2v3" +set __exe__="C:\Program Files\Nuke11.2v3\Nuke11.2.exe" -studio +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/nukestudio11.3.bat b/pype/settings/defaults/system_settings/launchers/windows/nukestudio11.3.bat new file mode 100644 index 0000000000..62c8718873 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/nukestudio11.3.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeStudio11.3v1" +set __exe__="C:\Program Files\Nuke11.3v1\Nuke11.3.exe" --studio +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/nukestudio12.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nukestudio12.0.bat new file mode 100644 index 0000000000..488232bcbf --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/nukestudio12.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeStudio12.0v1" +set __exe__="C:\Program Files\Nuke12.0v1\Nuke12.0.exe" --studio +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/nukex10.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nukex10.0.bat new file mode 100644 index 0000000000..1759706a7b --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/nukex10.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeX10.0v4" +set __exe__="C:\Program Files\Nuke10.0v4\Nuke10.0.exe" -nukex +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/nukex11.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nukex11.0.bat new file mode 100644 index 0000000000..b554a7b6fa --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/nukex11.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeX11.0v4" +set __exe__="C:\Program Files\Nuke11.0v4\Nuke11.0.exe" --nukex +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/nukex11.2.bat b/pype/settings/defaults/system_settings/launchers/windows/nukex11.2.bat new file mode 100644 index 0000000000..a4cb5dec5c --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/nukex11.2.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeX11.2v3" +set __exe__="C:\Program Files\Nuke11.2v3\Nuke11.2.exe" --nukex +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/nukex11.3.bat b/pype/settings/defaults/system_settings/launchers/windows/nukex11.3.bat new file mode 100644 index 0000000000..490b55cf4c --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/nukex11.3.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeX11.3v1" +set __exe__="C:\Program Files\Nuke11.3v1\Nuke11.3.exe" --nukex +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/nukex12.0.bat b/pype/settings/defaults/system_settings/launchers/windows/nukex12.0.bat new file mode 100644 index 0000000000..26adf0d3f1 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/nukex12.0.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="NukeX12.0v1" +set __exe__="C:\Program Files\Nuke12.0v1\Nuke12.0.exe" --nukex +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/photoshop_2020.bat b/pype/settings/defaults/system_settings/launchers/windows/photoshop_2020.bat new file mode 100644 index 0000000000..6b90922ef6 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/photoshop_2020.bat @@ -0,0 +1,15 @@ +@echo off + +set __app__="Photoshop 2020" +set __exe__="C:\Program Files\Adobe\Adobe Photoshop 2020\Photoshop.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% cmd.exe /k "%PYPE_PYTHON_EXE% -c ^"import avalon.photoshop;avalon.photoshop.launch("%__exe__%")^"" + +goto :eof + +pause + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/premiere_pro_2019.bat b/pype/settings/defaults/system_settings/launchers/windows/premiere_pro_2019.bat new file mode 100644 index 0000000000..4886737d2f --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/premiere_pro_2019.bat @@ -0,0 +1,14 @@ +@echo off + +set __app__="Adobe Premiere Pro" +set __exe__="C:\Program Files\Adobe\Adobe Premiere Pro CC 2019\Adobe Premiere Pro.exe" +if not exist %__exe__% goto :missing_app + +python -u %PREMIERA_PATH%\init.py +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/premiere_pro_2020.bat b/pype/settings/defaults/system_settings/launchers/windows/premiere_pro_2020.bat new file mode 100644 index 0000000000..14662d3be3 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/premiere_pro_2020.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Adobe Premiere Pro" +set __exe__="C:\Program Files\Adobe\Adobe Premiere Pro 2020\Adobe Premiere Pro.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/python3.bat b/pype/settings/defaults/system_settings/launchers/windows/python3.bat new file mode 100644 index 0000000000..c7c116fe72 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/python3.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Python36" +set __exe__="C:\Python36\python.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/resolve_16.bat b/pype/settings/defaults/system_settings/launchers/windows/resolve_16.bat new file mode 100644 index 0000000000..1a5d964e6b --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/resolve_16.bat @@ -0,0 +1,17 @@ +@echo off + +set __app__="Resolve" +set __appy__="Resolve Python Console" +set __exe__="C:/Program Files/Blackmagic Design/DaVinci Resolve/Resolve.exe" +set __py__="%PYTHON36_RESOLVE%/python.exe" + +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %* +IF "%RESOLVE_DEV%"=="True" (start %__appy__% %__py__% -i %PRE_PYTHON_SCRIPT%) + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/shell.bat b/pype/settings/defaults/system_settings/launchers/windows/shell.bat new file mode 100644 index 0000000000..eb0895364f --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/shell.bat @@ -0,0 +1,2 @@ +@echo off +start cmd diff --git a/pype/settings/defaults/system_settings/launchers/windows/storyboardpro_7.bat b/pype/settings/defaults/system_settings/launchers/windows/storyboardpro_7.bat new file mode 100644 index 0000000000..122edac572 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/storyboardpro_7.bat @@ -0,0 +1,13 @@ +@echo off + +set __app__="Storyboard Pro 7" +set __exe__="C:/Program Files (x86)/Toon Boom Animation/Toon Boom Storyboard Pro 7/win64/bin/StoryboardPro.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% cmd.exe /k "python -c ^"import avalon.storyboardpro;avalon.storyboardpro.launch("%__exe__%")^"" + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/launchers/windows/unreal.bat b/pype/settings/defaults/system_settings/launchers/windows/unreal.bat new file mode 100644 index 0000000000..7771aaa5a5 --- /dev/null +++ b/pype/settings/defaults/system_settings/launchers/windows/unreal.bat @@ -0,0 +1,11 @@ +set __app__="Unreal Editor" +set __exe__="%AVALON_CURRENT_UNREAL_ENGINE%\Engine\Binaries\Win64\UE4Editor.exe" +if not exist %__exe__% goto :missing_app + +start %__app__% %__exe__% %PYPE_UNREAL_PROJECT_FILE% %* + +goto :eof + +:missing_app + echo ERROR: %__app__% not found in %__exe__% + exit /B 1 diff --git a/pype/settings/defaults/system_settings/muster/templates_mapping.json b/pype/settings/defaults/system_settings/muster/templates_mapping.json new file mode 100644 index 0000000000..0c09113515 --- /dev/null +++ b/pype/settings/defaults/system_settings/muster/templates_mapping.json @@ -0,0 +1,19 @@ +{ + "3delight": 41, + "arnold": 46, + "arnold_sf": 57, + "gelato": 30, + "harware": 3, + "krakatoa": 51, + "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 +} \ No newline at end of file diff --git a/pype/settings/defaults/system_settings/standalone_publish/families.json b/pype/settings/defaults/system_settings/standalone_publish/families.json new file mode 100644 index 0000000000..d05941cc26 --- /dev/null +++ b/pype/settings/defaults/system_settings/standalone_publish/families.json @@ -0,0 +1,90 @@ +{ + "create_look": { + "name": "look", + "label": "Look", + "family": "look", + "icon": "paint-brush", + "defaults": ["Main"], + "help": "Shader connections defining shape look" + }, + "create_model": { + "name": "model", + "label": "Model", + "family": "model", + "icon": "cube", + "defaults": ["Main", "Proxy", "Sculpt"], + "help": "Polygonal static geometry" + }, + "create_workfile": { + "name": "workfile", + "label": "Workfile", + "family": "workfile", + "icon": "cube", + "defaults": ["Main"], + "help": "Working scene backup" + }, + "create_camera": { + "name": "camera", + "label": "Camera", + "family": "camera", + "icon": "video-camera", + "defaults": ["Main"], + "help": "Single baked camera" + }, + "create_pointcache": { + "name": "pointcache", + "label": "Pointcache", + "family": "pointcache", + "icon": "gears", + "defaults": ["Main"], + "help": "Alembic pointcache for animated data" + }, + "create_rig": { + "name": "rig", + "label": "Rig", + "family": "rig", + "icon": "wheelchair", + "defaults": ["Main"], + "help": "Artist-friendly rig with controls" + }, + "create_layout": { + "name": "layout", + "label": "Layout", + "family": "layout", + "icon": "cubes", + "defaults": ["Main"], + "help": "Simple scene for animators with camera" + }, + "create_plate": { + "name": "plate", + "label": "Plate", + "family": "plate", + "icon": "camera", + "defaults": ["Main", "BG", "Reference"], + "help": "Plates for compositors" + }, + "create_matchmove": { + "name": "matchmove", + "label": "Matchmove script", + "family": "matchmove", + "icon": "empire", + "defaults": ["Camera", "Object", "Mocap"], + "help": "Script exported from matchmoving application" + }, + "create_images": { + "name": "image", + "label": "Image file", + "family": "image", + "icon": "image", + "defaults": ["ConceptArt", "Reference", "Texture", "MattePaint"], + "help": "Holder for all kinds of image data" + }, + "create_editorial": { + "name": "editorial", + "label": "Editorial", + "family": "editorial", + "icon": "image", + "defaults": ["Main"], + "help": "Editorial files to generate shots." + } +} diff --git a/pype/settings/lib.py b/pype/settings/lib.py new file mode 100644 index 0000000000..388557ca9b --- /dev/null +++ b/pype/settings/lib.py @@ -0,0 +1,258 @@ +import os +import json +import logging +import copy + +log = logging.getLogger(__name__) + +# Metadata keys for work with studio and project overrides +OVERRIDEN_KEY = "__overriden_keys__" +# NOTE key popping not implemented yet +POP_KEY = "__pop_key__" + +# Folder where studio overrides are stored +STUDIO_OVERRIDES_PATH = os.environ["PYPE_PROJECT_CONFIGS"] + +# File where studio's system overrides are stored +SYSTEM_SETTINGS_KEY = "system_settings" +SYSTEM_SETTINGS_PATH = os.path.join( + STUDIO_OVERRIDES_PATH, SYSTEM_SETTINGS_KEY + ".json" +) + +# File where studio's default project overrides are stored +PROJECT_SETTINGS_KEY = "project_settings" +PROJECT_SETTINGS_FILENAME = PROJECT_SETTINGS_KEY + ".json" +PROJECT_SETTINGS_PATH = os.path.join( + STUDIO_OVERRIDES_PATH, PROJECT_SETTINGS_FILENAME +) + +PROJECT_ANATOMY_KEY = "project_anatomy" +PROJECT_ANATOMY_FILENAME = PROJECT_ANATOMY_KEY + ".json" +PROJECT_ANATOMY_PATH = os.path.join( + STUDIO_OVERRIDES_PATH, PROJECT_ANATOMY_FILENAME +) + +# Path to default settings +DEFAULTS_DIR = os.path.join(os.path.dirname(__file__), "defaults") + +# Variable where cache of default settings are stored +_DEFAULT_SETTINGS = None + + +def reset_default_settings(): + global _DEFAULT_SETTINGS + _DEFAULT_SETTINGS = None + + +def default_settings(): + global _DEFAULT_SETTINGS + if _DEFAULT_SETTINGS is None: + _DEFAULT_SETTINGS = load_jsons_from_dir(DEFAULTS_DIR) + return _DEFAULT_SETTINGS + + +def load_json(fpath): + # Load json data + with open(fpath, "r") as opened_file: + lines = opened_file.read().splitlines() + + # prepare json string + standard_json = "" + for line in lines: + # Remove all whitespace on both sides + line = line.strip() + + # Skip blank lines + if len(line) == 0: + continue + + standard_json += line + + # Check if has extra commas + extra_comma = False + if ",]" in standard_json or ",}" in standard_json: + extra_comma = True + standard_json = standard_json.replace(",]", "]") + standard_json = standard_json.replace(",}", "}") + + if extra_comma: + log.error("Extra comma in json file: \"{}\"".format(fpath)) + + # return empty dict if file is empty + if standard_json == "": + return {} + + # Try to parse string + try: + return json.loads(standard_json) + + except json.decoder.JSONDecodeError: + # Return empty dict if it is first time that decode error happened + return {} + + # Repreduce the exact same exception but traceback contains better + # information about position of error in the loaded json + try: + with open(fpath, "r") as opened_file: + json.load(opened_file) + + except json.decoder.JSONDecodeError: + log.warning( + "File has invalid json format \"{}\"".format(fpath), + exc_info=True + ) + + return {} + + +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 load_jsons_from_dir(path, *args, **kwargs): + 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(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 studio_system_settings(): + if os.path.exists(SYSTEM_SETTINGS_PATH): + return load_json(SYSTEM_SETTINGS_PATH) + return {} + + +def studio_project_settings(): + if os.path.exists(PROJECT_SETTINGS_PATH): + return load_json(PROJECT_SETTINGS_PATH) + return {} + + +def studio_project_anatomy(): + if os.path.exists(PROJECT_ANATOMY_PATH): + return load_json(PROJECT_ANATOMY_PATH) + return {} + + +def path_to_project_overrides(project_name): + return os.path.join( + STUDIO_OVERRIDES_PATH, + project_name, + PROJECT_SETTINGS_FILENAME + ) + + +def path_to_project_anatomy(project_name): + return os.path.join( + STUDIO_OVERRIDES_PATH, + project_name, + PROJECT_ANATOMY_FILENAME + ) + + +def project_settings_overrides(project_name): + if not project_name: + return {} + + path_to_json = path_to_project_overrides(project_name) + if not os.path.exists(path_to_json): + return {} + return load_json(path_to_json) + + +def project_anatomy_overrides(project_name): + if not project_name: + return {} + + path_to_json = path_to_project_anatomy(project_name) + if not os.path.exists(path_to_json): + return {} + return load_json(path_to_json) + + +def merge_overrides(global_dict, override_dict): + if OVERRIDEN_KEY in override_dict: + overriden_keys = set(override_dict.pop(OVERRIDEN_KEY)) + else: + overriden_keys = set() + + for key, value in override_dict.items(): + if value == POP_KEY: + global_dict.pop(key) + + elif ( + key in overriden_keys + or key not in global_dict + ): + global_dict[key] = value + + elif isinstance(value, dict) and isinstance(global_dict[key], dict): + global_dict[key] = merge_overrides(global_dict[key], value) + + else: + global_dict[key] = value + return global_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 system_settings(): + default_values = default_settings()[SYSTEM_SETTINGS_KEY] + studio_values = studio_system_settings() + return apply_overrides(default_values, studio_values) + + +def project_settings(project_name): + default_values = default_settings()[PROJECT_SETTINGS_KEY] + studio_values = studio_project_settings() + + studio_overrides = apply_overrides(default_values, studio_values) + + project_overrides = project_settings_overrides(project_name) + + return apply_overrides(studio_overrides, project_overrides) diff --git a/pype/tools/settings/__init__.py b/pype/tools/settings/__init__.py new file mode 100644 index 0000000000..7df121f06e --- /dev/null +++ b/pype/tools/settings/__init__.py @@ -0,0 +1,7 @@ +from settings import style, MainWidget + + +__all__ = ( + "style", + "MainWidget" +) diff --git a/pype/tools/settings/__main__.py b/pype/tools/settings/__main__.py new file mode 100644 index 0000000000..55a38b3604 --- /dev/null +++ b/pype/tools/settings/__main__.py @@ -0,0 +1,18 @@ +import sys + +import settings +from Qt import QtWidgets, QtGui + + +if __name__ == "__main__": + app = QtWidgets.QApplication(sys.argv) + + stylesheet = settings.style.load_stylesheet() + app.setStyleSheet(stylesheet) + app.setWindowIcon(QtGui.QIcon(settings.style.app_icon_path())) + + develop = "-d" in sys.argv or "--develop" in sys.argv + widget = settings.MainWidget(develop) + widget.show() + + sys.exit(app.exec_()) diff --git a/pype/tools/settings/settings/__init__.py b/pype/tools/settings/settings/__init__.py new file mode 100644 index 0000000000..0c2fd6d4bb --- /dev/null +++ b/pype/tools/settings/settings/__init__.py @@ -0,0 +1,8 @@ +from . import style +from .widgets import MainWidget + + +__all__ = ( + "style", + "MainWidget" +) diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/0_project_gui_schema.json b/pype/tools/settings/settings/gui_schemas/projects_schema/0_project_gui_schema.json new file mode 100644 index 0000000000..fa7c6a366d --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/0_project_gui_schema.json @@ -0,0 +1,32 @@ +{ + "key": "project", + "type": "dict-invisible", + "children": [ + { + "type": "anatomy", + "key": "project_anatomy", + "children": [ + { + "type": "anatomy_roots", + "key": "roots", + "is_file": true + }, { + "type": "anatomy_templates", + "key": "templates", + "is_file": true + } + ] + }, { + "type": "dict-invisible", + "key": "project_settings", + "children": [ + { + "type": "schema", + "children": [ + "1_plugins_gui_schema" + ] + } + ] + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json b/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json new file mode 100644 index 0000000000..6bb14463c1 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/projects_schema/1_plugins_gui_schema.json @@ -0,0 +1,619 @@ +{ + "type": "dict", + "collapsable": true, + "key": "plugins", + "label": "Plugins", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "celaction", + "label": "CelAction", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractCelactionDeadline", + "label": "ExtractCelactionDeadline", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "dict-form", + "children": [ + { + "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" + } + ] + } + ] + } + ] + } + ] + }, { + "type": "dict", + "collapsable": true, + "key": "ftrack", + "label": "Ftrack", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": 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", + "collapsable": true, + "key": "global", + "label": "Global", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "IntegrateMasterVersion", + "label": "IntegrateMasterVersion", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractJpegEXR", + "label": "ExtractJpegEXR", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "dict-invisible", + "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", + "collapsable": true, + "key": "ExtractReview", + "label": "ExtractReview", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "raw-json", + "key": "profiles", + "label": "Profiles" + } + ] + }, { + "type": "dict", + "collapsable": true, + "key": "ExtractBurnin", + "label": "ExtractBurnin", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "dict", + "collapsable": true, + "key": "options", + "label": "Burnin formating options", + "children": [ + { + "type": "number", + "key": "font_size", + "label": "Font size" + }, { + "type": "number", + "key": "opacity", + "label": "Font opacity" + }, { + "type": "number", + "key": "bg_opacity", + "label": "Background opacity" + }, { + "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": "raw-json", + "key": "profiles", + "label": "Burnin profiles" + } + ] + }, { + "type": "dict", + "collapsable": true, + "key": "IntegrateAssetNew", + "label": "IntegrateAssetNew", + "is_group": true, + "children": [ + { + "type": "raw-json", + "key": "template_name_profiles", + "label": "template_name_profiles" + } + ] + }, { + "type": "dict", + "collapsable": 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": "dict", + "collapsable": true, + "key": "maya", + "label": "Maya", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "ValidateModelName", + "label": "Validate Model Name", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "text", + "key": "material_file", + "label": "Material File" + }, { + "type": "text", + "key": "regex", + "label": "Validation regex" + } + ] + }, { + "type": "dict", + "collapsable": true, + "key": "ValidateAssemblyName", + "label": "Validate Assembly Name", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, { + "type": "dict", + "collapsable": true, + "key": "ValidateShaderName", + "label": "ValidateShaderName", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "text", + "key": "regex", + "label": "Validation regex" + } + ] + }, { + "type": "dict", + "collapsable": true, + "key": "ValidateMeshHasOverlappingUVs", + "label": "ValidateMeshHasOverlappingUVs", + "checkbox_key": "enabled", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + } + ] + }, { + "type": "raw-json", + "key": "workfile_build", + "label": "Workfile Build logic", + "is_file": true + } + ] + }, { + "type": "dict", + "collapsable": true, + "key": "nuke", + "label": "Nuke", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "create", + "label": "Create plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": false, + "key": "CreateWriteRender", + "label": "CreateWriteRender", + "is_group": true, + "children": [ + { + "type": "text", + "key": "fpath_template", + "label": "Path template" + } + ] + }, { + "type": "dict", + "collapsable": false, + "key": "CreateWritePrerender", + "label": "CreateWritePrerender", + "is_group": true, + "children": [ + { + "type": "text", + "key": "fpath_template", + "label": "Path template" + } + ] + } + ] + }, { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": 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", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ValidateNukeWriteKnobs", + "label": "ValidateNukeWriteKnobs", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + }, { + "type": "raw-json", + "key": "knobs", + "label": "Knobs" + } + ] + }, { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "ExtractReviewDataLut", + "label": "ExtractReviewDataLut", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, { + "type": "dict", + "collapsable": 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", + "collapsable": true, + "key": "ExtractSlateFrame", + "label": "ExtractSlateFrame", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "viewer_lut_raw", + "label": "Viewer LUT raw" + } + ] + }, { + "type": "dict", + "collapsable": 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" + } + ] + } + ] + }, { + "type": "raw-json", + "key": "workfile_build", + "label": "Workfile Build logic", + "is_file": true + } + ] + }, { + "type": "dict", + "collapsable": true, + "key": "nukestudio", + "label": "NukeStudio", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "publish", + "label": "Publish plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "checkbox_key": "enabled", + "key": "CollectInstanceVersion", + "label": "Collect Instance Version", + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "enabled", + "label": "Enabled" + } + ] + }, { + "type": "dict", + "collapsable": 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": "dict", + "collapsable": true, + "key": "resolve", + "label": "DaVinci Resolve", + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "create", + "label": "Creator plugins", + "is_file": true, + "children": [ + { + "type": "dict", + "collapsable": true, + "key": "CreateShotClip", + "label": "Create Shot Clip", + "is_group": true, + "children": [ + { + "type": "text", + "key": "clipName", + "label": "Clip name template" + }, { + "type": "text", + "key": "folder", + "label": "Folder" + }, { + "type": "number", + "key": "steps", + "label": "Steps" + } + ] + } + + ] + } + ] + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/0_system_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/0_system_gui_schema.json new file mode 100644 index 0000000000..b16545111c --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/0_system_gui_schema.json @@ -0,0 +1,34 @@ +{ + "key": "system", + "type": "dict-invisible", + "children": [ + { + "type": "dict-invisible", + "key": "global", + "children": [{ + "type": "schema", + "children": [ + "1_tray_items", + "1_applications_gui_schema", + "1_tools_gui_schema", + "1_intents_gui_schema" + ] + }] + }, { + "type": "dict-invisible", + "key": "muster", + "children": [{ + "type": "dict-modifiable", + "object_type": "number", + "input_modifiers": { + "minimum": 0, + "maximum": 300 + }, + "is_group": true, + "key": "templates_mapping", + "label": "Muster - Templates mapping", + "is_file": true + }] + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_applications_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_applications_gui_schema.json new file mode 100644 index 0000000000..48f8ecbd7c --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_applications_gui_schema.json @@ -0,0 +1,144 @@ +{ + "key": "applications", + "type": "dict", + "label": "Applications", + "collapsable": true, + "is_group": true, + "is_file": true, + "children": [ + { + "type": "dict-form", + "children": [ + { + "type": "boolean", + "key": "blender_2.80", + "label": "Blender 2.80" + }, { + "type": "boolean", + "key": "blender_2.81", + "label": "Blender 2.81" + }, { + "type": "boolean", + "key": "blender_2.82", + "label": "Blender 2.82" + }, { + "type": "boolean", + "key": "blender_2.83", + "label": "Blender 2.83" + }, { + "type": "boolean", + "key": "celaction_local", + "label": "Celaction Local" + }, { + "type": "boolean", + "key": "celaction_remote", + "label": "Celaction Remote" + }, { + "type": "boolean", + "key": "harmony_17", + "label": "Harmony 17" + }, { + "type": "boolean", + "key": "maya_2017", + "label": "Autodest Maya 2017" + }, { + "type": "boolean", + "key": "maya_2018", + "label": "Autodest Maya 2018" + }, { + "type": "boolean", + "key": "maya_2019", + "label": "Autodest Maya 2019" + }, { + "type": "boolean", + "key": "maya_2020", + "label": "Autodest Maya 2020" + }, { + "key": "nuke_10.0", + "type": "boolean", + "label": "Nuke 10.0" + }, { + "type": "boolean", + "key": "nuke_11.2", + "label": "Nuke 11.2" + }, { + "type": "boolean", + "key": "nuke_11.3", + "label": "Nuke 11.3" + }, { + "type": "boolean", + "key": "nuke_12.0", + "label": "Nuke 12.0" + }, { + "type": "boolean", + "key": "nukex_10.0", + "label": "NukeX 10.0" + }, { + "type": "boolean", + "key": "nukex_11.2", + "label": "NukeX 11.2" + }, { + "type": "boolean", + "key": "nukex_11.3", + "label": "NukeX 11.3" + }, { + "type": "boolean", + "key": "nukex_12.0", + "label": "NukeX 12.0" + }, { + "type": "boolean", + "key": "nukestudio_10.0", + "label": "NukeStudio 10.0" + }, { + "type": "boolean", + "key": "nukestudio_11.2", + "label": "NukeStudio 11.2" + }, { + "type": "boolean", + "key": "nukestudio_11.3", + "label": "NukeStudio 11.3" + }, { + "type": "boolean", + "key": "nukestudio_12.0", + "label": "NukeStudio 12.0" + }, { + "type": "boolean", + "key": "houdini_16", + "label": "Houdini 16" + }, { + "type": "boolean", + "key": "houdini_16.5", + "label": "Houdini 16.5" + }, { + "type": "boolean", + "key": "houdini_17", + "label": "Houdini 17" + }, { + "type": "boolean", + "key": "houdini_18", + "label": "Houdini 18" + }, { + "type": "boolean", + "key": "premiere_2019", + "label": "Premiere 2019" + }, { + "type": "boolean", + "key": "premiere_2020", + "label": "Premiere 2020" + }, { + "type": "boolean", + "key": "resolve_16", + "label": "BM DaVinci Resolve 16" + }, { + "type": "boolean", + "key": "storyboardpro_7", + "label": "Storyboard Pro 7" + }, { + "type": "boolean", + "key": "unreal_4.24", + "label": "Unreal Editor 4.24" + } + ] + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json new file mode 100644 index 0000000000..a884dcb31e --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_examples.json @@ -0,0 +1,234 @@ +{ + "key": "example_dict", + "label": "Examples", + "type": "dict", + "is_file": true, + "children": [ + { + "key": "dict_wrapper", + "type": "dict-invisible", + "children": [ + { + "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": "text", + "input_modifiers": { + "multiline": true + } + }, { + "type": "list", + "key": "list_item_of_floats", + "label": "List of floats", + "object_type": "number", + "input_modifiers": { + "decimal": 3, + "minimum": 1000, + "maximum": 2000 + } + }, { + "type": "dict-modifiable", + "key": "modifiable_dict_of_integers", + "label": "Modifiable dict of integers", + "object_type": "number", + "input_modifiers": { + "decimal": 0, + "minimum": 10, + "maximum": 100 + } + }, { + "type": "path-widget", + "key": "single_path_input", + "label": "Single path input", + "multiplatform": false, + "multipath": false + }, { + "type": "path-widget", + "key": "multi_path_input", + "label": "Multi path input", + "multiplatform": false, + "multipath": true + }, { + "type": "path-widget", + "key": "single_os_specific_path_input", + "label": "Single OS specific path input", + "multiplatform": true, + "multipath": false + }, { + "type": "path-widget", + "key": "multi_os_specific_path_input", + "label": "Multi OS specific path input", + "multiplatform": true, + "multipath": true + }, { + "key": "collapsable", + "type": "dict", + "label": "collapsable dictionary", + "collapsable": true, + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "_nothing", + "label": "Exmaple input" + } + ] + }, { + "key": "collapsable_expanded", + "type": "dict", + "label": "collapsable dictionary, expanded on creation", + "collapsable": true, + "collapsed": false, + "is_group": true, + "children": [ + { + "type": "boolean", + "key": "_nothing", + "label": "Exmaple input" + } + ] + }, { + "key": "not_collapsable", + "type": "dict", + "label": "Not collapsable", + "collapsable": 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": "dict-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 ########" + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_intents_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_intents_gui_schema.json new file mode 100644 index 0000000000..0c252d2ca9 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_intents_gui_schema.json @@ -0,0 +1,20 @@ +{ + "key": "intent", + "type": "dict", + "label": "Intent Setting", + "collapsable": true, + "is_group": true, + "is_file": true, + "children": [ + { + "type": "dict-modifiable", + "object_type": "text", + "key": "items", + "label": "Intent Key/Label" + }, { + "type": "text", + "key": "default", + "label": "Default intent" + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_tools_gui_schema.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_tools_gui_schema.json new file mode 100644 index 0000000000..d9540eeb3e --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_tools_gui_schema.json @@ -0,0 +1,32 @@ +{ + "key": "tools", + "type": "dict", + "label": "Tools", + "collapsable": true, + "is_group": true, + "is_file": true, + "children": [ + { + "type": "dict-form", + "children": [ + { + "key": "mtoa_3.0.1", + "type": "boolean", + "label": "Arnold Maya 3.0.1" + }, { + "key": "mtoa_3.1.1", + "type": "boolean", + "label": "Arnold Maya 3.1.1" + }, { + "key": "mtoa_3.2.0", + "type": "boolean", + "label": "Arnold Maya 3.2.0" + }, { + "key": "yeti_2.1.2", + "type": "boolean", + "label": "Yeti 2.1.2" + } + ] + } + ] +} diff --git a/pype/tools/settings/settings/gui_schemas/system_schema/1_tray_items.json b/pype/tools/settings/settings/gui_schemas/system_schema/1_tray_items.json new file mode 100644 index 0000000000..6da974a415 --- /dev/null +++ b/pype/tools/settings/settings/gui_schemas/system_schema/1_tray_items.json @@ -0,0 +1,125 @@ +{ + "key": "tray_modules", + "type": "dict", + "label": "Modules", + "collapsable": true, + "is_group": true, + "is_file": true, + "children": [ + { + "key": "item_usage", + "type": "dict-invisible", + "children": [ + { + "type": "dict-form", + "children": [ + { + "type": "boolean", + "key": "User settings", + "label": "User settings" + }, { + "type": "boolean", + "key": "Ftrack", + "label": "Ftrack" + }, { + "type": "boolean", + "key": "Muster", + "label": "Muster" + }, { + "type": "boolean", + "key": "Avalon", + "label": "Avalon" + }, { + "type": "boolean", + "key": "Clockify", + "label": "Clockify" + }, { + "type": "boolean", + "key": "Standalone Publish", + "label": "Standalone Publish" + }, { + "type": "boolean", + "key": "Logging", + "label": "Logging" + }, { + "type": "boolean", + "key": "Idle Manager", + "label": "Idle Manager" + }, { + "type": "boolean", + "key": "Timers Manager", + "label": "Timers Manager" + }, { + "type": "boolean", + "key": "Rest Api", + "label": "Rest Api" + }, { + "type": "boolean", + "key": "Adobe Communicator", + "label": "Adobe Communicator" + } + ] + } + ] + }, { + "key": "attributes", + "type": "dict-invisible", + "children": [ + { + "type": "dict", + "key": "Rest Api", + "label": "Rest Api", + "collapsable": true, + "children": [ + { + "type": "number", + "key": "default_port", + "label": "Default Port", + "minimum": 1, + "maximum": 65535 + }, { + "type": "list", + "object_type": "number", + "key": "exclude_ports", + "label": "Exclude ports", + "input_modifiers": { + "minimum": 1, + "maximum": 65535 + } + } + ] + }, { + "type": "dict", + "key": "Timers Manager", + "label": "Timers Manager", + "collapsable": true, + "children": [ + { + "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", + "collapsable": true, + "children": [ + { + "type": "text", + "key": "workspace_name", + "label": "Workspace name" + } + ] + } + ] + } + ] +} diff --git a/pype/tools/settings/settings/style/__init__.py b/pype/tools/settings/settings/style/__init__.py new file mode 100644 index 0000000000..a8f202d97b --- /dev/null +++ b/pype/tools/settings/settings/style/__init__.py @@ -0,0 +1,12 @@ +import os + + +def load_stylesheet(): + style_path = os.path.join(os.path.dirname(__file__), "style.css") + with open(style_path, "r") as style_file: + stylesheet = style_file.read() + return stylesheet + + +def app_icon_path(): + return os.path.join(os.path.dirname(__file__), "pype_icon.png") diff --git a/pype/tools/settings/settings/style/pype_icon.png b/pype/tools/settings/settings/style/pype_icon.png new file mode 100644 index 0000000000..bfacf6eeed Binary files /dev/null and b/pype/tools/settings/settings/style/pype_icon.png differ diff --git a/pype/tools/settings/settings/style/style.css b/pype/tools/settings/settings/style/style.css new file mode 100644 index 0000000000..38f69fef50 --- /dev/null +++ b/pype/tools/settings/settings/style/style.css @@ -0,0 +1,315 @@ +QWidget { + color: #bfccd6; + background-color: #293742; + font-size: 12px; + border-radius: 0px; +} + +QMenu { + border: 1px solid #555555; + background-color: #1d272f; +} + +QMenu::item { + padding: 5px 10px 5px 10px; + border-left: 5px solid #313131; +} + +QMenu::item:selected { + border-left-color: #61839e; + background-color: #222d37; +} +QCheckBox { + spacing: 0px; +} +QCheckBox::indicator {} +QCheckBox::indicator:focus {} + +QLineEdit, QSpinBox, QDoubleSpinBox, QPlainTextEdit, QTextEdit { + border: 1px solid #aaaaaa; + border-radius: 3px; + background-color: #1d272f; +} + +QLineEdit:disabled, QSpinBox:disabled, QDoubleSpinBox:disabled, QPlainTextEdit:disabled, QTextEdit:disabled, QPushButton:disabled { + background-color: #4e6474; +} + +QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QPlainTextEdit:focus, QTextEdit:focus { + border: 1px solid #ffffff; +} +QToolButton { + background: transparent; +} + +QLabel { + background: transparent; + color: #7390a5; +} +QLabel:hover {color: #839caf;} + +QLabel[state="studio"] {color: #bfccd6;} +QLabel[state="studio"]:hover {color: #ffffff;} +QLabel[state="modified"] {color: #137cbd;} +QLabel[state="modified"]:hover {color: #1798e8;} +QLabel[state="overriden-modified"] {color: #137cbd;} +QLabel[state="overriden-modified"]:hover {color: #1798e8;} +QLabel[state="overriden"] {color: #ff8c1a;} +QLabel[state="overriden"]:hover {color: #ffa64d;} +QLabel[state="invalid"] {color: #ad2e2e;} +QLabel[state="invalid"]:hover {color: #ad2e2e;} + + +QWidget[input-state="studio"] {border-color: #bfccd6;} +QWidget[input-state="modified"] {border-color: #137cbd;} +QWidget[input-state="overriden-modified"] {border-color: #137cbd;} +QWidget[input-state="overriden"] {border-color: #ff8c1a;} +QWidget[input-state="invalid"] {border-color: #ad2e2e;} + +QPushButton { + border: 1px solid #aaaaaa; + border-radius: 3px; + padding: 5px; +} +QPushButton:hover { + background-color: #31424e; +} +QPushButton[btn-type="tool-item"] { + border: 1px solid #bfccd6; + border-radius: 10px; +} + +QPushButton[btn-type="tool-item"]:hover { + border-color: #137cbd; + color: #137cbd; + background-color: transparent; +} + +QPushButton[btn-type="expand-toggle"] { + background: #1d272f; +} + +#GroupWidget { + border-bottom: 1px solid #1d272f; +} + +#ProjectListWidget QListView { + border: 1px solid #aaaaaa; + background: #1d272f; +} +#ProjectListWidget QLabel { + background: transparent; + font-weight: bold; +} + +#DictKey[state="studio"] {border-color: #bfccd6;} +#DictKey[state="modified"] {border-color: #137cbd;} +#DictKey[state="overriden"] {border-color: #00f;} +#DictKey[state="overriden-modified"] {border-color: #0f0;} +#DictKey[state="invalid"] {border-color: #ad2e2e;} + +#DictLabel { + font-weight: bold; +} + +#ContentWidget { + background-color: transparent; +} +#ContentWidget[content_state="hightlighted"] { + background-color: rgba(19, 26, 32, 15%); +} + +#SideLineWidget { + background-color: #31424e; + border-style: solid; + border-color: #3b4f5e; + border-left-width: 3px; + border-bottom-width: 0px; + border-right-width: 0px; + border-top-width: 0px; +} + +#SideLineWidget:hover { + border-color: #58768d; +} + +#SideLineWidget[state="child-studio"] {border-color: #455c6e;} +#SideLineWidget[state="child-studio"]:hover {border-color: #62839d;} + +#SideLineWidget[state="child-modified"] {border-color: #106aa2;} +#SideLineWidget[state="child-modified"]:hover {border-color: #137cbd;} + +#SideLineWidget[state="child-invalid"] {border-color: #ad2e2e;} +#SideLineWidget[state="child-invalid"]:hover {border-color: #c93636;} + +#SideLineWidget[state="child-overriden"] {border-color: #e67300;} +#SideLineWidget[state="child-overriden"]:hover {border-color: #ff8c1a;} + +#SideLineWidget[state="child-overriden-modified"] {border-color: #106aa2;} +#SideLineWidget[state="child-overriden-modified"]:hover {border-color: #137cbd;} + +#MainWidget { + background: #141a1f; +} + +#SplitterItem { + background-color: #1d272f; +} + +QTabWidget::pane { + border-top-style: none; +} + +QTabBar { + background: transparent; +} + +QTabBar::tab { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + padding: 5px; +} + +QTabBar::tab:selected { + background: #293742; + border-color: #9B9B9B; + border-bottom-color: #C2C7CB; +} + +QTabBar::tab:!selected { + margin-top: 2px; + background: #1d272f; +} + +QTabBar::tab:!selected:hover { + background: #3b4f5e; +} + + + +QTabBar::tab:first:selected { + margin-left: 0; +} + +QTabBar::tab:last:selected { + margin-right: 0; +} + +QTabBar::tab:only-one { + margin: 0; +} + +QScrollBar:horizontal { + height: 15px; + margin: 3px 15px 3px 15px; + border: 1px transparent #1d272f; + border-radius: 4px; + background-color: #1d272f; +} + +QScrollBar::handle:horizontal { + background-color: #61839e; + min-width: 5px; + border-radius: 4px; +} + +QScrollBar::add-line:horizontal { + margin: 0px 3px 0px 3px; + border-image: url(:/qss_icons/rc/right_arrow_disabled.png); + width: 10px; + height: 10px; + subcontrol-position: right; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal { + margin: 0px 3px 0px 3px; + border-image: url(:/qss_icons/rc/left_arrow_disabled.png); + height: 10px; + width: 10px; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::add-line:horizontal:hover,QScrollBar::add-line:horizontal:on { + border-image: url(:/qss_icons/rc/right_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: right; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:horizontal:hover, QScrollBar::sub-line:horizontal:on { + border-image: url(:/qss_icons/rc/left_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: left; + subcontrol-origin: margin; +} + +QScrollBar::up-arrow:horizontal, QScrollBar::down-arrow:horizontal { + background: none; +} + +QScrollBar::add-page:horizontal, QScrollBar::sub-page:horizontal { + background: none; +} + +QScrollBar:vertical { + background-color: #1d272f; + width: 15px; + margin: 15px 3px 15px 3px; + border: 1px transparent #1d272f; + border-radius: 4px; +} + +QScrollBar::handle:vertical { + background-color: #61839e; + min-height: 5px; + border-radius: 4px; +} + +QScrollBar::sub-line:vertical { + margin: 3px 0px 3px 0px; + border-image: url(:/qss_icons/rc/up_arrow_disabled.png); + height: 10px; + width: 10px; + subcontrol-position: top; + subcontrol-origin: margin; +} + +QScrollBar::add-line:vertical { + margin: 3px 0px 3px 0px; + border-image: url(:/qss_icons/rc/down_arrow_disabled.png); + height: 10px; + width: 10px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::sub-line:vertical:hover,QScrollBar::sub-line:vertical:on { + + border-image: url(:/qss_icons/rc/up_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: top; + subcontrol-origin: margin; +} + + +QScrollBar::add-line:vertical:hover, QScrollBar::add-line:vertical:on { + border-image: url(:/qss_icons/rc/down_arrow.png); + height: 10px; + width: 10px; + subcontrol-position: bottom; + subcontrol-origin: margin; +} + +QScrollBar::up-arrow:vertical, QScrollBar::down-arrow:vertical { + background: none; +} + + +QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { + background: none; +} diff --git a/pype/tools/settings/settings/widgets/__init__.py b/pype/tools/settings/settings/widgets/__init__.py new file mode 100644 index 0000000000..361fd9d23d --- /dev/null +++ b/pype/tools/settings/settings/widgets/__init__.py @@ -0,0 +1,9 @@ +from .window import MainWidget +from . import item_types +from . import anatomy_types + +__all__ = [ + "MainWidget", + "item_types", + "anatomy_types" +] diff --git a/pype/tools/settings/settings/widgets/anatomy_types.py b/pype/tools/settings/settings/widgets/anatomy_types.py new file mode 100644 index 0000000000..6d7b3292ce --- /dev/null +++ b/pype/tools/settings/settings/widgets/anatomy_types.py @@ -0,0 +1,758 @@ +from Qt import QtWidgets, QtCore +from .widgets import ExpandingWidget +from .item_types import ( + SettingObject, ModifiableDict, PathWidget, RawJsonWidget +) +from .lib import NOT_SET, TypeToKlass, CHILD_OFFSET, METADATA_KEY + + +class AnatomyWidget(QtWidgets.QWidget, SettingObject): + value_changed = QtCore.Signal(object) + template_keys = ( + "project[name]", + "project[code]", + "asset", + "task", + "subset", + "family", + "version", + "ext", + "representation" + ) + default_exmaple_data = { + "project": { + "name": "ProjectPype", + "code": "pp", + }, + "asset": "sq01sh0010", + "task": "compositing", + "subset": "renderMain", + "family": "render", + "version": 1, + "ext": ".png", + "representation": "png" + } + + def __init__( + self, input_data, parent, as_widget=False, label_widget=None + ): + if as_widget: + raise TypeError( + "`AnatomyWidget` does not allow to be used as widget." + ) + super(AnatomyWidget, self).__init__(parent) + self.setObjectName("AnatomyWidget") + + self.initial_attributes(input_data, parent, as_widget) + + self.key = input_data["key"] + + children_data = input_data["children"] + roots_input_data = {} + templates_input_data = {} + for child in children_data: + if child["type"] == "anatomy_roots": + roots_input_data = child + elif child["type"] == "anatomy_templates": + templates_input_data = child + + self.root_widget = RootsWidget(roots_input_data, self) + self.templates_widget = TemplatesWidget(templates_input_data, self) + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + body_widget = ExpandingWidget("Anatomy", self) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + layout.addWidget(body_widget) + + content_widget = QtWidgets.QWidget(body_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, 0) + content_layout.setSpacing(5) + + content_layout.addWidget(self.root_widget) + content_layout.addWidget(self.templates_widget) + + body_widget.set_content_widget(content_widget) + + self.body_widget = body_widget + self.label_widget = body_widget.label_widget + + self.root_widget.value_changed.connect(self._on_value_change) + self.templates_widget.value_changed.connect(self._on_value_change) + + def update_default_values(self, parent_values): + self._state = None + self._child_state = None + + if isinstance(parent_values, dict): + value = parent_values.get(self.key, NOT_SET) + else: + value = NOT_SET + + self.root_widget.update_default_values(value) + self.templates_widget.update_default_values(value) + + def update_studio_values(self, parent_values): + self._state = None + self._child_state = None + + if isinstance(parent_values, dict): + value = parent_values.get(self.key, NOT_SET) + else: + value = NOT_SET + + self.root_widget.update_studio_values(value) + self.templates_widget.update_studio_values(value) + + def apply_overrides(self, parent_values): + # Make sure this is set to False + self._state = None + self._child_state = None + + value = NOT_SET + if parent_values is not NOT_SET: + value = parent_values.get(self.key, value) + + self.root_widget.apply_overrides(value) + self.templates_widget.apply_overrides(value) + + def set_value(self, value): + raise TypeError("AnatomyWidget does not allow to use `set_value`") + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self.hierarchical_style_update() + + self.value_changed.emit(self) + + def update_style(self, is_overriden=None): + child_has_studio_override = self.child_has_studio_override + child_modified = self.child_modified + child_invalid = self.child_invalid + child_state = self.style_state( + child_has_studio_override, + child_invalid, + self.child_overriden, + child_modified + ) + if child_state: + child_state = "child-{}".format(child_state) + + if child_state != self._child_state: + self.body_widget.side_line_widget.setProperty("state", child_state) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) + self._child_state = child_state + + def hierarchical_style_update(self): + self.root_widget.hierarchical_style_update() + self.templates_widget.hierarchical_style_update() + self.update_style() + + @property + def child_has_studio_override(self): + return ( + self.root_widget.child_has_studio_override + or self.templates_widget.child_has_studio_override + ) + + @property + def child_modified(self): + return ( + self.root_widget.child_modified + or self.templates_widget.child_modified + ) + + @property + def child_overriden(self): + return ( + self.root_widget.child_overriden + or self.templates_widget.child_overriden + ) + + @property + def child_invalid(self): + return ( + self.root_widget.child_invalid + or self.templates_widget.child_invalid + ) + + def set_as_overriden(self): + self.root_widget.set_as_overriden() + self.templates_widget.set_as_overriden() + + def remove_overrides(self): + self.root_widget.remove_overrides() + self.templates_widget.remove_overrides() + + def reset_to_pype_default(self): + self.root_widget.reset_to_pype_default() + self.templates_widget.reset_to_pype_default() + + def set_studio_default(self): + self.root_widget.set_studio_default() + self.templates_widget.set_studio_default() + + def discard_changes(self): + self.root_widget.discard_changes() + self.templates_widget.discard_changes() + + def overrides(self): + if self.child_overriden: + return self.config_value(), True + return NOT_SET, False + + def item_value(self): + output = {} + output.update(self.root_widget.config_value()) + output.update(self.templates_widget.config_value()) + return output + + def studio_overrides(self): + if ( + self.root_widget.child_has_studio_override + or self.templates_widget.child_has_studio_override + ): + groups = [self.root_widget.key, self.templates_widget.key] + value = self.config_value() + value[self.key][METADATA_KEY] = {"groups": groups} + return value, True + return NOT_SET, False + + def config_value(self): + return {self.key: self.item_value()} + + +class RootsWidget(QtWidgets.QWidget, SettingObject): + value_changed = QtCore.Signal(object) + + def __init__(self, input_data, parent): + super(RootsWidget, self).__init__(parent) + self.setObjectName("RootsWidget") + + input_data["is_group"] = True + self.initial_attributes(input_data, parent, False) + + self.key = input_data["key"] + + self._multiroot_state = None + self.default_is_multiroot = False + self.studio_is_multiroot = False + self.was_multiroot = NOT_SET + + checkbox_widget = QtWidgets.QWidget(self) + multiroot_label = QtWidgets.QLabel( + "Use multiple roots", checkbox_widget + ) + multiroot_checkbox = QtWidgets.QCheckBox(checkbox_widget) + + checkbox_layout = QtWidgets.QHBoxLayout(checkbox_widget) + checkbox_layout.addWidget(multiroot_label, 0) + checkbox_layout.addWidget(multiroot_checkbox, 1) + + body_widget = ExpandingWidget("Roots", self) + content_widget = QtWidgets.QWidget(body_widget) + + path_widget_data = { + "key": self.key, + "multipath": False, + "multiplatform": True + } + singleroot_widget = PathWidget( + path_widget_data, self, + as_widget=True, parent_widget=content_widget + ) + multiroot_data = { + "key": self.key, + "object_type": "path-widget", + "expandable": False, + "input_modifiers": { + "multiplatform": True + } + } + multiroot_widget = ModifiableDict( + multiroot_data, self, + as_widget=True, parent_widget=content_widget + ) + + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.addWidget(checkbox_widget) + content_layout.addWidget(singleroot_widget) + content_layout.addWidget(multiroot_widget) + + body_widget.set_content_widget(content_widget) + self.label_widget = body_widget.label_widget + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.addWidget(body_widget) + + self.body_widget = body_widget + self.multiroot_label = multiroot_label + self.multiroot_checkbox = multiroot_checkbox + self.singleroot_widget = singleroot_widget + self.multiroot_widget = multiroot_widget + + multiroot_checkbox.stateChanged.connect(self._on_multiroot_checkbox) + singleroot_widget.value_changed.connect(self._on_value_change) + multiroot_widget.value_changed.connect(self._on_value_change) + + self._on_multiroot_checkbox() + + @property + def is_multiroot(self): + return self.multiroot_checkbox.isChecked() + + def update_default_values(self, parent_values): + self._state = None + self._multiroot_state = None + self._is_modified = False + + if isinstance(parent_values, dict): + value = parent_values.get(self.key, NOT_SET) + else: + value = NOT_SET + + is_multiroot = False + if isinstance(value, dict): + for _value in value.values(): + if isinstance(_value, dict): + is_multiroot = True + break + + self.default_is_multiroot = is_multiroot + self.was_multiroot = is_multiroot + self.set_multiroot(is_multiroot) + + self._has_studio_override = False + self._had_studio_override = False + if is_multiroot: + for _value in value.values(): + singleroot_value = _value + break + + multiroot_value = value + else: + singleroot_value = value + multiroot_value = {"": value} + + self.singleroot_widget.update_default_values(singleroot_value) + self.multiroot_widget.update_default_values(multiroot_value) + + def update_studio_values(self, parent_values): + self._state = None + self._multiroot_state = None + self._is_modified = False + + if isinstance(parent_values, dict): + value = parent_values.get(self.key, NOT_SET) + else: + value = NOT_SET + + if value is NOT_SET: + is_multiroot = self.default_is_multiroot + self.studio_is_multiroot = NOT_SET + self._has_studio_override = False + self._had_studio_override = False + else: + is_multiroot = False + if isinstance(value, dict): + for _value in value.values(): + if isinstance(_value, dict): + is_multiroot = True + break + self.studio_is_multiroot = is_multiroot + self._has_studio_override = True + self._had_studio_override = True + + self.was_multiroot = is_multiroot + self.set_multiroot(is_multiroot) + + if is_multiroot: + self.multiroot_widget.update_studio_values(value) + else: + self.singleroot_widget.update_studio_values(value) + + def apply_overrides(self, parent_values): + # Make sure this is set to False + self._state = None + self._multiroot_state = None + self._is_modified = False + + value = NOT_SET + if parent_values is not NOT_SET: + value = parent_values.get(self.key, value) + + if value is NOT_SET: + is_multiroot = self.studio_is_multiroot + if is_multiroot is NOT_SET: + is_multiroot = self.default_is_multiroot + else: + is_multiroot = False + if isinstance(value, dict): + for _value in value.values(): + if isinstance(_value, dict): + is_multiroot = True + break + + self.was_multiroot = is_multiroot + self.set_multiroot(is_multiroot) + + if is_multiroot: + self._is_overriden = value is not NOT_SET + self._was_overriden = bool(self._is_overriden) + self.multiroot_widget.apply_overrides(value) + else: + self._is_overriden = value is not NOT_SET + self._was_overriden = bool(self._is_overriden) + self.singleroot_widget.apply_overrides(value) + + def hierarchical_style_update(self): + self.singleroot_widget.hierarchical_style_update() + self.multiroot_widget.hierarchical_style_update() + self.update_style() + + def update_style(self): + multiroot_state = self.style_state( + self.has_studio_override, + False, + False, + self.was_multiroot != self.is_multiroot + ) + if multiroot_state != self._multiroot_state: + self.multiroot_label.setProperty("state", multiroot_state) + self.multiroot_label.style().polish(self.multiroot_label) + self._multiroot_state = multiroot_state + + state = self.style_state( + self.has_studio_override, + self.child_invalid, + self.is_overriden, + self.is_modified + ) + if self._state == state: + return + + if state: + child_state = "child-{}".format(state) + else: + child_state = "" + + self.body_widget.side_line_widget.setProperty("state", child_state) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state + + def _on_multiroot_checkbox(self): + self.set_multiroot() + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if item is not None and ( + (self.is_multiroot and item != self.multiroot_widget) + or (not self.is_multiroot and item != self.singleroot_widget) + ): + return + + if self.is_group and self.is_overidable: + self._is_overriden = True + + self._is_modified = ( + self.was_multiroot != self.is_multiroot + or self.child_modified + ) + + self.update_style() + + self.value_changed.emit(self) + + def _from_single_to_multi(self): + single_value = self.singleroot_widget.item_value() + mutli_value = self.multiroot_widget.item_value() + first_key = None + for key in mutli_value.keys(): + first_key = key + break + + if first_key is None: + first_key = "" + + mutli_value[first_key] = single_value + + self.multiroot_widget.set_value(mutli_value) + + def _from_multi_to_single(self): + mutli_value = self.multiroot_widget.all_item_values() + for value in mutli_value.values(): + single_value = value + break + + self.singleroot_widget.set_value(single_value) + + def set_multiroot(self, is_multiroot=None): + if is_multiroot is None: + is_multiroot = self.is_multiroot + if is_multiroot: + self._from_single_to_multi() + else: + self._from_multi_to_single() + + if is_multiroot != self.is_multiroot: + self.multiroot_checkbox.setChecked(is_multiroot) + + self.singleroot_widget.setVisible(not is_multiroot) + self.multiroot_widget.setVisible(is_multiroot) + + self._on_value_change() + + @property + def child_has_studio_override(self): + if self.is_multiroot: + return self.multiroot_widget.has_studio_override + else: + return self.singleroot_widget.has_studio_override + + @property + def child_modified(self): + if self.is_multiroot: + return self.multiroot_widget.child_modified + else: + return self.singleroot_widget.child_modified + + @property + def child_overriden(self): + if self.is_multiroot: + return ( + self.multiroot_widget.is_overriden + or self.multiroot_widget.child_overriden + ) + else: + return ( + self.singleroot_widget.is_overriden + or self.singleroot_widget.child_overriden + ) + + @property + def child_invalid(self): + if self.is_multiroot: + return self.multiroot_widget.child_invalid + else: + return self.singleroot_widget.child_invalid + + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + + if self.studio_is_multiroot is NOT_SET: + self.set_multiroot(self.default_is_multiroot) + else: + self.set_multiroot(self.studio_is_multiroot) + + if self.is_multiroot: + self.multiroot_widget.remove_overrides() + else: + self.singleroot_widget.remove_overrides() + + def reset_to_pype_default(self): + self.set_multiroot(self.default_is_multiroot) + if self.is_multiroot: + self.multiroot_widget.reset_to_pype_default() + else: + self.singleroot_widget.reset_to_pype_default() + self._has_studio_override = False + + def set_studio_default(self): + if self.is_multiroot: + self.multiroot_widget.reset_to_pype_default() + else: + self.singleroot_widget.reset_to_pype_default() + self._has_studio_override = True + + def discard_changes(self): + self._is_overriden = self._was_overriden + self._is_modified = False + if self._is_overriden: + self.set_multiroot(self.was_multiroot) + else: + if self.studio_is_multiroot is NOT_SET: + self.set_multiroot(self.default_is_multiroot) + else: + self.set_multiroot(self.studio_is_multiroot) + + if self.is_multiroot: + self.multiroot_widget.discard_changes() + else: + self.singleroot_widget.discard_changes() + + self._is_modified = self.child_modified + self._has_studio_override = self._had_studio_override + + def set_as_overriden(self): + self._is_overriden = True + self.singleroot_widget.set_as_overriden() + self.multiroot_widget.set_as_overriden() + + def item_value(self): + if self.is_multiroot: + return self.multiroot_widget.item_value() + else: + return self.singleroot_widget.item_value() + + def config_value(self): + return {self.key: self.item_value()} + + +class TemplatesWidget(QtWidgets.QWidget, SettingObject): + value_changed = QtCore.Signal(object) + + def __init__(self, input_data, parent): + super(TemplatesWidget, self).__init__(parent) + + input_data["is_group"] = True + self.initial_attributes(input_data, parent, False) + + self.key = input_data["key"] + + body_widget = ExpandingWidget("Templates", self) + content_widget = QtWidgets.QWidget(body_widget) + body_widget.set_content_widget(content_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + + template_input_data = { + "key": self.key + } + self.body_widget = body_widget + self.label_widget = body_widget.label_widget + self.value_input = RawJsonWidget( + template_input_data, self, + label_widget=self.label_widget + ) + content_layout.addWidget(self.value_input) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + layout.addWidget(body_widget) + + self.value_input.value_changed.connect(self._on_value_change) + + def _on_value_change(self, item): + self.update_style() + + self.value_changed.emit(self) + + def update_default_values(self, values): + self._state = None + self.value_input.update_default_values(values) + + def update_studio_values(self, values): + self._state = None + self.value_input.update_studio_values(values) + + def apply_overrides(self, parent_values): + self._state = None + self.value_input.apply_overrides(parent_values) + + def hierarchical_style_update(self): + self.value_input.hierarchical_style_update() + self.update_style() + + def update_style(self): + state = self.style_state( + self.has_studio_override, + self.child_invalid, + self.child_overriden, + self.child_modified + ) + if self._state == state: + return + + if state: + child_state = "child-{}".format(state) + else: + child_state = "" + + self.body_widget.side_line_widget.setProperty("state", child_state) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state + + @property + def is_modified(self): + return self.value_input.is_modified + + @property + def is_overriden(self): + return self._is_overriden + + @property + def has_studio_override(self): + return self.value_input._has_studio_override + + @property + def child_has_studio_override(self): + return self.value_input.child_has_studio_override + + @property + def child_modified(self): + return self.value_input.child_modified + + @property + def child_overriden(self): + return self.value_input.child_overriden + + @property + def child_invalid(self): + return self.value_input.child_invalid + + def remove_overrides(self): + self.value_input.remove_overrides() + + def reset_to_pype_default(self): + self.value_input.reset_to_pype_default() + + def set_studio_default(self): + self.value_input.set_studio_default() + + def discard_changes(self): + self.value_input.discard_changes() + + def set_as_overriden(self): + self.value_input.set_as_overriden() + + def overrides(self): + if not self.child_overriden: + return NOT_SET, False + return self.config_value(), True + + def item_value(self): + return self.value_input.item_value() + + def config_value(self): + return self.value_input.config_value() + + +TypeToKlass.types["anatomy"] = AnatomyWidget +TypeToKlass.types["anatomy_roots"] = AnatomyWidget +TypeToKlass.types["anatomy_templates"] = AnatomyWidget diff --git a/pype/tools/settings/settings/widgets/base.py b/pype/tools/settings/settings/widgets/base.py new file mode 100644 index 0000000000..dbcc380daf --- /dev/null +++ b/pype/tools/settings/settings/widgets/base.py @@ -0,0 +1,735 @@ +import os +import json +from Qt import QtWidgets, QtCore, QtGui +from pype.settings.lib import ( + SYSTEM_SETTINGS_KEY, + SYSTEM_SETTINGS_PATH, + PROJECT_SETTINGS_KEY, + PROJECT_SETTINGS_PATH, + PROJECT_ANATOMY_KEY, + PROJECT_ANATOMY_PATH, + + DEFAULTS_DIR, + + reset_default_settings, + default_settings, + + studio_system_settings, + studio_project_settings, + studio_project_anatomy, + + project_settings_overrides, + project_anatomy_overrides, + + path_to_project_overrides, + path_to_project_anatomy +) +from .widgets import UnsavedChangesDialog +from . import lib +from avalon import io +from avalon.vendor import qtawesome + + +class SystemWidget(QtWidgets.QWidget): + is_overidable = False + has_studio_override = _has_studio_override = False + is_overriden = _is_overriden = False + is_group = _is_group = False + any_parent_is_group = _any_parent_is_group = False + + def __init__(self, develop_mode, parent=None): + super(SystemWidget, self).__init__(parent) + + self.develop_mode = develop_mode + self._hide_studio_overrides = False + self._ignore_value_changes = False + + self.input_fields = [] + + scroll_widget = QtWidgets.QScrollArea(self) + scroll_widget.setObjectName("GroupWidget") + content_widget = QtWidgets.QWidget(scroll_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(3, 3, 3, 3) + content_layout.setSpacing(0) + content_layout.setAlignment(QtCore.Qt.AlignTop) + content_widget.setLayout(content_layout) + + scroll_widget.setWidgetResizable(True) + scroll_widget.setWidget(content_widget) + + self.scroll_widget = scroll_widget + self.content_layout = content_layout + self.content_widget = content_widget + + footer_widget = QtWidgets.QWidget() + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + + if self.develop_mode: + save_as_default_btn = QtWidgets.QPushButton("Save as Default") + save_as_default_btn.clicked.connect(self._save_as_defaults) + + refresh_icon = qtawesome.icon("fa.refresh", color="white") + refresh_button = QtWidgets.QPushButton() + refresh_button.setIcon(refresh_icon) + refresh_button.clicked.connect(self._on_refresh) + + hide_studio_overrides = QtWidgets.QCheckBox() + hide_studio_overrides.setChecked(self._hide_studio_overrides) + hide_studio_overrides.stateChanged.connect( + self._on_hide_studio_overrides + ) + + hide_studio_overrides_widget = QtWidgets.QWidget() + hide_studio_overrides_layout = QtWidgets.QHBoxLayout( + hide_studio_overrides_widget + ) + _label_widget = QtWidgets.QLabel( + "Hide studio overrides", hide_studio_overrides_widget + ) + hide_studio_overrides_layout.addWidget(_label_widget) + hide_studio_overrides_layout.addWidget(hide_studio_overrides) + + footer_layout.addWidget(save_as_default_btn, 0) + footer_layout.addWidget(refresh_button, 0) + footer_layout.addWidget(hide_studio_overrides_widget, 0) + + save_btn = QtWidgets.QPushButton("Save") + spacer_widget = QtWidgets.QWidget() + footer_layout.addWidget(spacer_widget, 1) + footer_layout.addWidget(save_btn, 0) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + self.setLayout(layout) + + layout.addWidget(scroll_widget, 1) + layout.addWidget(footer_widget, 0) + + save_btn.clicked.connect(self._save) + + self.reset() + + def any_parent_overriden(self): + return False + + @property + def ignore_value_changes(self): + return self._ignore_value_changes + + @ignore_value_changes.setter + def ignore_value_changes(self, value): + self._ignore_value_changes = value + if value is False: + self.hierarchical_style_update() + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + + def reset(self): + reset_default_settings() + + if self.content_layout.count() != 0: + for widget in self.input_fields: + self.content_layout.removeWidget(widget) + widget.deleteLater() + self.input_fields.clear() + + self.schema = lib.gui_schema("system_schema", "0_system_gui_schema") + self.keys = self.schema.get("keys", []) + self.add_children_gui(self.schema) + self._update_values() + self.hierarchical_style_update() + + def _save(self): + has_invalid = False + for item in self.input_fields: + if item.child_invalid: + has_invalid = True + + if has_invalid: + invalid_items = [] + for item in self.input_fields: + invalid_items.extend(item.get_invalid()) + msg_box = QtWidgets.QMessageBox( + QtWidgets.QMessageBox.Warning, + "Invalid input", + "There is invalid value in one of inputs." + " Please lead red color and fix them." + ) + msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok) + msg_box.exec_() + + first_invalid_item = invalid_items[0] + self.scroll_widget.ensureWidgetVisible(first_invalid_item) + if first_invalid_item.isVisible(): + first_invalid_item.setFocus(True) + return + + _data = {} + for input_field in self.input_fields: + value, is_group = input_field.studio_overrides() + if value is not lib.NOT_SET: + _data.update(value) + + values = lib.convert_gui_data_to_overrides(_data.get("system", {})) + + dirpath = os.path.dirname(SYSTEM_SETTINGS_PATH) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + print("Saving data to:", SYSTEM_SETTINGS_PATH) + with open(SYSTEM_SETTINGS_PATH, "w") as file_stream: + json.dump(values, file_stream, indent=4) + + self._update_values() + + def _on_refresh(self): + self.reset() + + def _on_hide_studio_overrides(self, state): + self._hide_studio_overrides = (state == QtCore.Qt.Checked) + self._update_values() + self.hierarchical_style_update() + + def _save_as_defaults(self): + output = {} + for item in self.input_fields: + output.update(item.config_value()) + + for key in reversed(self.keys): + _output = {key: output} + output = _output + + all_values = {} + for item in self.input_fields: + all_values.update(item.config_value()) + + for key in reversed(self.keys): + _all_values = {key: all_values} + all_values = _all_values + + # Skip first key + all_values = all_values["system"] + + prject_defaults_dir = os.path.join( + DEFAULTS_DIR, SYSTEM_SETTINGS_KEY + ) + keys_to_file = lib.file_keys_from_schema(self.schema) + for key_sequence in keys_to_file: + # Skip first key + key_sequence = key_sequence[1:] + subpath = "/".join(key_sequence) + ".json" + + new_values = all_values + for key in key_sequence: + new_values = new_values[key] + + output_path = os.path.join(prject_defaults_dir, subpath) + dirpath = os.path.dirname(output_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + print("Saving data to: ", subpath) + with open(output_path, "w") as file_stream: + json.dump(new_values, file_stream, indent=4) + + reset_default_settings() + + self._update_values() + self.hierarchical_style_update() + + def _update_values(self): + self.ignore_value_changes = True + + default_values = { + "system": default_settings()[SYSTEM_SETTINGS_KEY] + } + for input_field in self.input_fields: + input_field.update_default_values(default_values) + + if self._hide_studio_overrides: + system_values = lib.NOT_SET + else: + system_values = {"system": studio_system_settings()} + for input_field in self.input_fields: + input_field.update_studio_values(system_values) + + self.ignore_value_changes = False + + def add_children_gui(self, child_configuration): + item_type = child_configuration["type"] + klass = lib.TypeToKlass.types.get(item_type) + item = klass(child_configuration, self) + self.input_fields.append(item) + self.content_layout.addWidget(item) + + +class ProjectListView(QtWidgets.QListView): + left_mouse_released_at = QtCore.Signal(QtCore.QModelIndex) + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + index = self.indexAt(event.pos()) + self.left_mouse_released_at.emit(index) + super(ProjectListView, self).mouseReleaseEvent(event) + + +class ProjectListWidget(QtWidgets.QWidget): + default = "< Default >" + project_changed = QtCore.Signal() + + def __init__(self, parent): + self._parent = parent + + self.current_project = None + + super(ProjectListWidget, self).__init__(parent) + self.setObjectName("ProjectListWidget") + + label_widget = QtWidgets.QLabel("Projects") + label_widget.setProperty("state", "studio") + project_list = ProjectListView(self) + project_list.setModel(QtGui.QStandardItemModel()) + + # Do not allow editing + project_list.setEditTriggers( + QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers + ) + # Do not automatically handle selection + project_list.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection) + + layout = QtWidgets.QVBoxLayout(self) + layout.setSpacing(3) + layout.addWidget(label_widget, 0) + layout.addWidget(project_list, 1) + + project_list.left_mouse_released_at.connect(self.on_item_clicked) + + self.project_list = project_list + + self.refresh() + + def on_item_clicked(self, new_index): + new_project_name = new_index.data(QtCore.Qt.DisplayRole) + if new_project_name is None: + return + + if self.current_project == new_project_name: + return + + save_changes = False + change_project = False + if self.validate_context_change(): + change_project = True + + else: + dialog = UnsavedChangesDialog(self) + result = dialog.exec_() + if result == 1: + save_changes = True + change_project = True + + elif result == 2: + change_project = True + + if save_changes: + self._parent._save() + + if change_project: + self.select_project(new_project_name) + self.current_project = new_project_name + self.project_changed.emit() + else: + self.select_project(self.current_project) + + def validate_context_change(self): + # TODO add check if project can be changed (is modified) + for item in self._parent.input_fields: + is_modified = item.child_modified + if is_modified: + return False + return True + + def project_name(self): + if self.current_project == self.default: + return None + return self.current_project + + def select_project(self, project_name): + model = self.project_list.model() + found_items = model.findItems(project_name) + if not found_items: + found_items = model.findItems(self.default) + + index = model.indexFromItem(found_items[0]) + self.project_list.selectionModel().clear() + self.project_list.selectionModel().setCurrentIndex( + index, QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent + ) + + def refresh(self): + selected_project = None + for index in self.project_list.selectedIndexes(): + selected_project = index.data(QtCore.Qt.DisplayRole) + break + + model = self.project_list.model() + model.clear() + items = [self.default] + io.install() + for project_doc in tuple(io.projects()): + items.append(project_doc["name"]) + + for item in items: + model.appendRow(QtGui.QStandardItem(item)) + + self.select_project(selected_project) + + self.current_project = self.project_list.currentIndex().data( + QtCore.Qt.DisplayRole + ) + + +class ProjectWidget(QtWidgets.QWidget): + has_studio_override = _has_studio_override = False + is_overriden = _is_overriden = False + is_group = _is_group = False + any_parent_is_group = _any_parent_is_group = False + + def __init__(self, develop_mode, parent=None): + super(ProjectWidget, self).__init__(parent) + + self.develop_mode = develop_mode + self._hide_studio_overrides = False + + self.is_overidable = False + self._ignore_value_changes = False + self.project_name = None + + self.input_fields = [] + + scroll_widget = QtWidgets.QScrollArea(self) + scroll_widget.setObjectName("GroupWidget") + content_widget = QtWidgets.QWidget(scroll_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(3, 3, 3, 3) + content_layout.setSpacing(0) + content_layout.setAlignment(QtCore.Qt.AlignTop) + content_widget.setLayout(content_layout) + + scroll_widget.setWidgetResizable(True) + scroll_widget.setWidget(content_widget) + + project_list_widget = ProjectListWidget(self) + content_layout.addWidget(project_list_widget) + + footer_widget = QtWidgets.QWidget() + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + + if self.develop_mode: + save_as_default_btn = QtWidgets.QPushButton("Save as Default") + save_as_default_btn.clicked.connect(self._save_as_defaults) + + refresh_icon = qtawesome.icon("fa.refresh", color="white") + refresh_button = QtWidgets.QPushButton() + refresh_button.setIcon(refresh_icon) + refresh_button.clicked.connect(self._on_refresh) + + hide_studio_overrides = QtWidgets.QCheckBox() + hide_studio_overrides.setChecked(self._hide_studio_overrides) + hide_studio_overrides.stateChanged.connect( + self._on_hide_studio_overrides + ) + + hide_studio_overrides_widget = QtWidgets.QWidget() + hide_studio_overrides_layout = QtWidgets.QHBoxLayout( + hide_studio_overrides_widget + ) + _label_widget = QtWidgets.QLabel( + "Hide studio overrides", hide_studio_overrides_widget + ) + hide_studio_overrides_layout.addWidget(_label_widget) + hide_studio_overrides_layout.addWidget(hide_studio_overrides) + + footer_layout.addWidget(save_as_default_btn, 0) + footer_layout.addWidget(refresh_button, 0) + footer_layout.addWidget(hide_studio_overrides_widget, 0) + + save_btn = QtWidgets.QPushButton("Save") + spacer_widget = QtWidgets.QWidget() + footer_layout.addWidget(spacer_widget, 1) + footer_layout.addWidget(save_btn, 0) + + configurations_widget = QtWidgets.QWidget() + configurations_layout = QtWidgets.QVBoxLayout(configurations_widget) + configurations_layout.setContentsMargins(0, 0, 0, 0) + configurations_layout.setSpacing(0) + + configurations_layout.addWidget(scroll_widget, 1) + configurations_layout.addWidget(footer_widget, 0) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + self.setLayout(layout) + + layout.addWidget(project_list_widget, 0) + layout.addWidget(configurations_widget, 1) + + save_btn.clicked.connect(self._save) + project_list_widget.project_changed.connect(self._on_project_change) + + self.project_list_widget = project_list_widget + self.scroll_widget = scroll_widget + self.content_layout = content_layout + self.content_widget = content_widget + + self.reset() + + def any_parent_overriden(self): + return False + + @property + def ignore_value_changes(self): + return self._ignore_value_changes + + @ignore_value_changes.setter + def ignore_value_changes(self, value): + self._ignore_value_changes = value + if value is False: + self.hierarchical_style_update() + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + + def reset(self): + if self.content_layout.count() != 0: + for widget in self.input_fields: + self.content_layout.removeWidget(widget) + widget.deleteLater() + self.input_fields.clear() + + self.schema = lib.gui_schema("projects_schema", "0_project_gui_schema") + self.keys = self.schema.get("keys", []) + self.add_children_gui(self.schema) + self._update_values() + self.hierarchical_style_update() + + def add_children_gui(self, child_configuration): + item_type = child_configuration["type"] + klass = lib.TypeToKlass.types.get(item_type) + item = klass(child_configuration, self) + self.input_fields.append(item) + self.content_layout.addWidget(item) + + def _on_project_change(self): + project_name = self.project_list_widget.project_name() + if project_name is None: + _project_overrides = lib.NOT_SET + _project_anatomy = lib.NOT_SET + self.is_overidable = False + else: + _project_overrides = project_settings_overrides(project_name) + _project_anatomy = project_anatomy_overrides(project_name) + self.is_overidable = True + + overrides = {"project": { + PROJECT_SETTINGS_KEY: lib.convert_overrides_to_gui_data( + _project_overrides + ), + PROJECT_ANATOMY_KEY: lib.convert_overrides_to_gui_data( + _project_anatomy + ) + }} + self.project_name = project_name + self.ignore_value_changes = True + for item in self.input_fields: + item.apply_overrides(overrides) + self.ignore_value_changes = False + + def _save_as_defaults(self): + output = {} + for item in self.input_fields: + output.update(item.config_value()) + + for key in reversed(self.keys): + _output = {key: output} + output = _output + + all_values = {} + for item in self.input_fields: + all_values.update(item.config_value()) + + for key in reversed(self.keys): + _all_values = {key: all_values} + all_values = _all_values + + # Skip first key + all_values = all_values["project"] + + keys_to_file = lib.file_keys_from_schema(self.schema) + for key_sequence in keys_to_file: + # Skip first key + key_sequence = key_sequence[1:] + subpath = "/".join(key_sequence) + ".json" + + new_values = all_values + for key in key_sequence: + new_values = new_values[key] + + output_path = os.path.join(DEFAULTS_DIR, subpath) + dirpath = os.path.dirname(output_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + print("Saving data to: ", subpath) + with open(output_path, "w") as file_stream: + json.dump(new_values, file_stream, indent=4) + + reset_default_settings() + + self._update_values() + self.hierarchical_style_update() + + def _save(self): + has_invalid = False + for item in self.input_fields: + if item.child_invalid: + has_invalid = True + + if has_invalid: + invalid_items = [] + for item in self.input_fields: + invalid_items.extend(item.get_invalid()) + msg_box = QtWidgets.QMessageBox( + QtWidgets.QMessageBox.Warning, + "Invalid input", + "There is invalid value in one of inputs." + " Please lead red color and fix them." + ) + msg_box.setStandardButtons(QtWidgets.QMessageBox.Ok) + msg_box.exec_() + + first_invalid_item = invalid_items[0] + self.scroll_widget.ensureWidgetVisible(first_invalid_item) + if first_invalid_item.isVisible(): + first_invalid_item.setFocus(True) + return + + if self.project_name is None: + self._save_studio_overrides() + else: + self._save_overrides() + + def _on_refresh(self): + self.reset() + + def _on_hide_studio_overrides(self, state): + self._hide_studio_overrides = (state == QtCore.Qt.Checked) + self._update_values() + self.hierarchical_style_update() + + def _save_overrides(self): + data = {} + for item in self.input_fields: + value, is_group = item.overrides() + if value is not lib.NOT_SET: + data.update(value) + + output_data = lib.convert_gui_data_to_overrides( + data.get("project") or {} + ) + + # Saving overrides data + project_overrides_data = output_data.get( + PROJECT_SETTINGS_KEY, {} + ) + project_overrides_json_path = path_to_project_overrides( + self.project_name + ) + dirpath = os.path.dirname(project_overrides_json_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + print("Saving data to:", project_overrides_json_path) + with open(project_overrides_json_path, "w") as file_stream: + json.dump(project_overrides_data, file_stream, indent=4) + + # Saving anatomy data + project_anatomy_data = output_data.get( + PROJECT_ANATOMY_KEY, {} + ) + project_anatomy_json_path = path_to_project_anatomy( + self.project_name + ) + dirpath = os.path.dirname(project_anatomy_json_path) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + print("Saving data to:", project_anatomy_json_path) + with open(project_anatomy_json_path, "w") as file_stream: + json.dump(project_anatomy_data, file_stream, indent=4) + + # Refill values with overrides + self._on_project_change() + + def _save_studio_overrides(self): + data = {} + for input_field in self.input_fields: + value, is_group = input_field.studio_overrides() + if value is not lib.NOT_SET: + data.update(value) + + output_data = lib.convert_gui_data_to_overrides( + data.get("project", {}) + ) + + # Project overrides data + project_overrides_data = output_data.get( + PROJECT_SETTINGS_KEY, {} + ) + dirpath = os.path.dirname(PROJECT_SETTINGS_PATH) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + print("Saving data to:", PROJECT_SETTINGS_PATH) + with open(PROJECT_SETTINGS_PATH, "w") as file_stream: + json.dump(project_overrides_data, file_stream, indent=4) + + # Project Anatomy data + project_anatomy_data = output_data.get( + PROJECT_ANATOMY_KEY, {} + ) + dirpath = os.path.dirname(PROJECT_ANATOMY_PATH) + if not os.path.exists(dirpath): + os.makedirs(dirpath) + + print("Saving data to:", PROJECT_ANATOMY_PATH) + with open(PROJECT_ANATOMY_PATH, "w") as file_stream: + json.dump(project_anatomy_data, file_stream, indent=4) + + # Update saved values + self._update_values() + + def _update_values(self): + self.ignore_value_changes = True + + default_values = {"project": default_settings()} + for input_field in self.input_fields: + input_field.update_default_values(default_values) + + if self._hide_studio_overrides: + studio_values = lib.NOT_SET + else: + studio_values = {"project": { + PROJECT_SETTINGS_KEY: studio_project_settings(), + PROJECT_ANATOMY_KEY: studio_project_anatomy() + }} + for input_field in self.input_fields: + input_field.update_studio_values(studio_values) + + self.ignore_value_changes = False diff --git a/pype/tools/settings/settings/widgets/item_types.py b/pype/tools/settings/settings/widgets/item_types.py new file mode 100644 index 0000000000..e2d59c2e69 --- /dev/null +++ b/pype/tools/settings/settings/widgets/item_types.py @@ -0,0 +1,3049 @@ +import json +import logging +import collections +from Qt import QtWidgets, QtCore, QtGui +from .widgets import ( + ExpandingWidget, + NumberSpinBox, + PathInput +) +from .lib import NOT_SET, METADATA_KEY, TypeToKlass, CHILD_OFFSET +from avalon.vendor import qtawesome + + +class SettingObject: + # `is_input_type` attribute says if has implemented item type methods + is_input_type = True + # each input must have implemented default value for development + # when defaults are not filled yet + default_input_value = NOT_SET + # will allow to show actions for the item type (disabled for proxies) + allow_actions = True + # default state of item type + default_state = "" + + @classmethod + def style_state(cls, is_invalid, is_overriden, is_modified): + """Return stylesheet state by intered booleans.""" + items = [] + if is_invalid: + items.append("invalid") + else: + if is_overriden: + items.append("overriden") + if is_modified: + items.append("modified") + return "-".join(items) or cls.default_state + + def _set_default_attributes(self): + """Create and reset attributes required for all item types. + + They may not be used in the item but are required to be set. + """ + # Default input attributes + self._has_studio_override = False + self._had_studio_override = False + + self._is_overriden = False + self._was_overriden = False + + self._is_modified = False + self._is_invalid = False + + self._is_nullable = False + self._as_widget = False + self._is_group = False + + self._any_parent_is_group = None + + # Parent input + self._parent = None + + # States of inputs + self._state = None + self._child_state = None + + # Attributes where values are stored + self.default_value = NOT_SET + self.studio_value = NOT_SET + self.override_value = NOT_SET + + # Log object + self._log = None + + # Only for develop mode + self.defaults_not_set = False + + def initial_attributes(self, input_data, parent, as_widget): + """Prepare attributes based on entered arguments. + + This method should be same for each item type. Few item types + may require to extend with specific attributes for their case. + """ + self._set_default_attributes() + + self._parent = parent + self._as_widget = as_widget + + self._is_group = input_data.get("is_group", False) + # TODO not implemented yet + self._is_nullable = input_data.get("is_nullable", False) + + any_parent_is_group = parent.is_group + if not any_parent_is_group: + any_parent_is_group = parent.any_parent_is_group + + self._any_parent_is_group = any_parent_is_group + + @property + def develop_mode(self): + """Tool is in develop mode or not. + + Returns: + bool + + """ + return self._parent.develop_mode + + @property + def log(self): + """Auto created logger for debugging.""" + if self._log is None: + self._log = logging.getLogger(self.__class__.__name__) + return self._log + + @property + def had_studio_override(self): + """Item had studio overrides on refresh. + + Returns: + bool + + """ + return self._had_studio_override + + @property + def has_studio_override(self): + """Item has studio override at the moment. + + With combination of `had_studio_override` is possible to know if item + has changes (not just value change). + + Returns: + bool + + """ + return self._has_studio_override or self._parent.has_studio_override + + @property + def is_group(self): + """Item represents key that can be overriden. + + Attribute `is_group` can be set to True only once in item hierarchy. + + Returns: + bool + + """ + return self._is_group + + @property + def any_parent_is_group(self): + """Any parent of item is group. + + Attribute holding this information is set during creation and + stored to `_any_parent_is_group`. + + Why is this information useful: If any parent is group and + the parent is set as overriden, this item is overriden too. + + Returns: + bool + + """ + if self._any_parent_is_group is None: + return super(SettingObject, self).any_parent_is_group + return self._any_parent_is_group + + @property + def is_modified(self): + """Has object any changes that require saving.""" + if self._is_modified or self.defaults_not_set: + return True + + if self.is_overidable: + return self.was_overriden != self.is_overriden + else: + return self.has_studio_override != self.had_studio_override + + @property + def is_overriden(self): + """Is object overriden so should be saved to overrides.""" + return self._is_overriden or self._parent.is_overriden + + @property + def was_overriden(self): + """Item had set value of project overrides on project change.""" + if self._as_widget: + return self._parent.was_overriden + return self._was_overriden + + @property + def is_invalid(self): + """Value set in is not valid.""" + return self._is_invalid + + @property + def is_nullable(self): + """Value of item can be set to None. + + NOT IMPLEMENTED! + """ + return self._is_nullable + + @property + def is_overidable(self): + """Should care about overrides.""" + return self._parent.is_overidable + + def any_parent_overriden(self): + """Any of parent objects up to top hiearchy item is overriden. + + Returns: + bool + + """ + if self._parent._is_overriden: + return True + return self._parent.any_parent_overriden() + + @property + def ignore_value_changes(self): + """Most of attribute changes are ignored on value change when True.""" + return self._parent.ignore_value_changes + + @ignore_value_changes.setter + def ignore_value_changes(self, value): + """Setter for global parent item to apply changes for all inputs.""" + self._parent.ignore_value_changes = value + + def config_value(self): + """Output for saving changes or overrides.""" + return {self.key: self.item_value()} + + @classmethod + def style_state( + cls, has_studio_override, is_invalid, is_overriden, is_modified + ): + items = [] + if is_invalid: + items.append("invalid") + else: + if is_overriden: + items.append("overriden") + if is_modified: + items.append("modified") + + if not items and has_studio_override: + items.append("studio") + + return "-".join(items) or cls.default_state + + def mouseReleaseEvent(self, event): + if self.allow_actions and event.button() == QtCore.Qt.RightButton: + menu = QtWidgets.QMenu() + + actions_mapping = {} + if self.child_modified: + action = QtWidgets.QAction("Discard changes") + actions_mapping[action] = self._discard_changes + menu.addAction(action) + + if ( + self.is_overidable + and not self.is_overriden + and not self.any_parent_is_group + ): + action = QtWidgets.QAction("Set project override") + actions_mapping[action] = self._set_as_overriden + menu.addAction(action) + + if ( + not self.is_overidable + and ( + self.has_studio_override + ) + ): + action = QtWidgets.QAction("Reset to pype default") + actions_mapping[action] = self._reset_to_pype_default + menu.addAction(action) + + if ( + not self.is_overidable + and not self.is_overriden + and not self.any_parent_is_group + and not self._had_studio_override + ): + action = QtWidgets.QAction("Set studio default") + actions_mapping[action] = self._set_studio_default + menu.addAction(action) + + if ( + not self.any_parent_overriden() + and (self.is_overriden or self.child_overriden) + ): + # TODO better label + action = QtWidgets.QAction("Remove project override") + actions_mapping[action] = self._remove_overrides + menu.addAction(action) + + if not actions_mapping: + action = QtWidgets.QAction("< No action >") + actions_mapping[action] = None + menu.addAction(action) + + result = menu.exec_(QtGui.QCursor.pos()) + if result: + to_run = actions_mapping[result] + if to_run: + to_run() + return + + mro = type(self).mro() + index = mro.index(self.__class__) + item = None + for idx in range(index + 1, len(mro)): + _item = mro[idx] + if hasattr(_item, "mouseReleaseEvent"): + item = _item + break + + if item: + return item.mouseReleaseEvent(self, event) + + def _discard_changes(self): + self.ignore_value_changes = True + self.discard_changes() + self.ignore_value_changes = False + + def discard_changes(self): + raise NotImplementedError( + "{} Method `discard_changes` not implemented!".format( + repr(self) + ) + ) + + def _set_studio_default(self): + self.ignore_value_changes = True + self.set_studio_default() + self.ignore_value_changes = False + + def set_studio_default(self): + raise NotImplementedError( + "{} Method `set_studio_default` not implemented!".format( + repr(self) + ) + ) + + def _reset_to_pype_default(self): + self.ignore_value_changes = True + self.reset_to_pype_default() + self.ignore_value_changes = False + + def reset_to_pype_default(self): + raise NotImplementedError( + "{} Method `reset_to_pype_default` not implemented!".format( + repr(self) + ) + ) + + def _remove_overrides(self): + self.ignore_value_changes = True + self.remove_overrides() + self.ignore_value_changes = False + + def remove_overrides(self): + raise NotImplementedError( + "{} Method `remove_overrides` not implemented!".format( + repr(self) + ) + ) + + def _set_as_overriden(self): + self.ignore_value_changes = True + self.set_as_overriden() + self.ignore_value_changes = False + + def set_as_overriden(self): + raise NotImplementedError( + "{} Method `set_as_overriden` not implemented!".format(repr(self)) + ) + + def hierarchical_style_update(self): + raise NotImplementedError( + "{} Method `hierarchical_style_update` not implemented!".format( + repr(self) + ) + ) + + def update_default_values(self, parent_values): + raise NotImplementedError( + "{} does not have implemented `update_default_values`".format(self) + ) + + def update_studio_values(self, parent_values): + raise NotImplementedError( + "{} does not have implemented `update_studio_values`".format(self) + ) + + def apply_overrides(self, parent_values): + raise NotImplementedError( + "{} does not have implemented `apply_overrides`".format(self) + ) + + @property + def child_has_studio_override(self): + """Any children item is modified.""" + raise NotImplementedError( + "{} does not have implemented `child_has_studio_override`".format( + self + ) + ) + + @property + def child_modified(self): + """Any children item is modified.""" + raise NotImplementedError( + "{} does not have implemented `child_modified`".format(self) + ) + + @property + def child_overriden(self): + """Any children item is overriden.""" + raise NotImplementedError( + "{} does not have implemented `child_overriden`".format(self) + ) + + @property + def child_invalid(self): + """Any children item does not have valid value.""" + raise NotImplementedError( + "{} does not have implemented `child_invalid`".format(self) + ) + + def get_invalid(self): + """Return invalid item types all down the hierarchy.""" + raise NotImplementedError( + "{} does not have implemented `get_invalid`".format(self) + ) + + def item_value(self): + """Value of an item without key.""" + raise NotImplementedError( + "Method `item_value` not implemented!" + ) + + def studio_value(self): + """Output for saving changes or overrides.""" + return {self.key: self.item_value()} + + +class InputObject(SettingObject): + def update_default_values(self, parent_values): + self._state = None + self._is_modified = False + + value = NOT_SET + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + if value is NOT_SET: + if self.develop_mode: + value = self.default_input_value + self.defaults_not_set = True + if value is NOT_SET: + raise NotImplementedError(( + "{} Does not have implemented" + " attribute `default_input_value`" + ).format(self)) + + else: + raise ValueError( + "Default value is not set. This is implementation BUG." + ) + + self.default_value = value + self._has_studio_override = False + self._had_studio_override = False + self.set_value(value) + + def update_studio_values(self, parent_values): + self._state = None + self._is_modified = False + + value = NOT_SET + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + self.studio_value = value + if value is not NOT_SET: + self._has_studio_override = True + self._had_studio_override = True + self.set_value(value) + + else: + self._has_studio_override = False + self._had_studio_override = False + self.set_value(self.default_value) + + def apply_overrides(self, parent_values): + self._is_modified = False + self._state = None + self._had_studio_override = bool(self._has_studio_override) + if self._as_widget: + override_value = parent_values + elif parent_values is NOT_SET or self.key not in parent_values: + override_value = NOT_SET + else: + override_value = parent_values[self.key] + + self.override_value = override_value + + if override_value is NOT_SET: + self._is_overriden = False + self._was_overriden = False + if self.has_studio_override: + value = self.studio_value + else: + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self.set_value(value) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_overidable: + self._is_overriden = True + else: + self._has_studio_override = True + + if self._is_invalid: + self._is_modified = True + elif self._is_overriden: + self._is_modified = self.item_value() != self.override_value + elif self._has_studio_override: + self._is_modified = self.item_value() != self.studio_value + else: + self._is_modified = self.item_value() != self.default_value + + self.update_style() + + self.value_changed.emit(self) + + def studio_overrides(self): + if not self.has_studio_override: + return NOT_SET, False + return self.config_value(), self.is_group + + def overrides(self): + if not self.is_overriden: + return NOT_SET, False + return self.config_value(), self.is_group + + def hierarchical_style_update(self): + self.update_style() + + def remove_overrides(self): + if self.has_studio_override: + self.set_value(self.studio_value) + else: + self.set_value(self.default_value) + self._is_overriden = False + self._is_modified = False + + def reset_to_pype_default(self): + self.set_value(self.default_value) + self._has_studio_override = False + + def set_studio_default(self): + self._has_studio_override = True + + def discard_changes(self): + self._is_overriden = self._was_overriden + self._has_studio_override = self._had_studio_override + if self.is_overidable: + if self._was_overriden and self.override_value is not NOT_SET: + self.set_value(self.override_value) + else: + if self._had_studio_override: + self.set_value(self.studio_value) + else: + self.set_value(self.default_value) + + if not self.is_overidable: + if self.has_studio_override: + self._is_modified = self.studio_value != self.item_value() + else: + self._is_modified = self.default_value != self.item_value() + self._is_overriden = False + return + + self._is_modified = False + self._is_overriden = self._was_overriden + + def set_as_overriden(self): + self._is_overriden = True + + @property + def child_has_studio_override(self): + return self._has_studio_override + + @property + def child_modified(self): + return self.is_modified + + @property + def child_overriden(self): + return self._is_overriden + + @property + def child_invalid(self): + return self.is_invalid + + def get_invalid(self): + output = [] + if self.is_invalid: + output.append(self) + return output + + def reset_children_attributes(self): + return + + +class BooleanWidget(QtWidgets.QWidget, InputObject): + default_input_value = True + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(BooleanWidget, self).__init__(parent_widget) + + self.initial_attributes(input_data, parent, as_widget) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + if not self._as_widget: + self.key = input_data["key"] + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + layout.addWidget(label_widget, 0) + self.label_widget = label_widget + + self.checkbox = QtWidgets.QCheckBox(self) + spacer = QtWidgets.QWidget(self) + layout.addWidget(self.checkbox, 0) + layout.addWidget(spacer, 1) + + spacer.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + self.setFocusProxy(self.checkbox) + + self.checkbox.stateChanged.connect(self._on_value_change) + + def set_value(self, value): + # Ignore value change because if `self.isChecked()` has same + # value as `value` the `_on_value_change` is not triggered + self.checkbox.setChecked(value) + + def update_style(self): + if self._as_widget: + if not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + else: + property_name = "state" + + self.label_widget.setProperty(property_name, state) + self.label_widget.style().polish(self.label_widget) + self._state = state + + def item_value(self): + return self.checkbox.isChecked() + + +class NumberWidget(QtWidgets.QWidget, InputObject): + default_input_value = 0 + value_changed = QtCore.Signal(object) + input_modifiers = ("minimum", "maximum", "decimal") + + def __init__( + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(NumberWidget, self).__init__(parent_widget) + + self.initial_attributes(input_data, parent, as_widget) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + kwargs = { + modifier: input_data.get(modifier) + for modifier in self.input_modifiers + if input_data.get(modifier) + } + self.input_field = NumberSpinBox(self, **kwargs) + + self.setFocusProxy(self.input_field) + + if not self._as_widget: + self.key = input_data["key"] + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0) + self.label_widget = label_widget + + layout.addWidget(self.input_field, 1) + + self.input_field.valueChanged.connect(self._on_value_change) + + def set_value(self, value): + self.input_field.setValue(value) + + def update_style(self): + if self._as_widget: + if not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.input_field + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.input_field.value() + + +class TextWidget(QtWidgets.QWidget, InputObject): + default_input_value = "" + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(TextWidget, self).__init__(parent_widget) + + self.initial_attributes(input_data, parent, as_widget) + + self.multiline = input_data.get("multiline", False) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + if self.multiline: + self.text_input = QtWidgets.QPlainTextEdit(self) + else: + self.text_input = QtWidgets.QLineEdit(self) + + self.setFocusProxy(self.text_input) + + layout_kwargs = {} + if self.multiline: + layout_kwargs["alignment"] = QtCore.Qt.AlignTop + + if not self._as_widget: + self.key = input_data["key"] + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0, **layout_kwargs) + self.label_widget = label_widget + + layout.addWidget(self.text_input, 1, **layout_kwargs) + + self.text_input.textChanged.connect(self._on_value_change) + + def set_value(self, value): + if self.multiline: + self.text_input.setPlainText(value) + else: + self.text_input.setText(value) + + def update_style(self): + if self._as_widget: + if not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) + + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.text_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + if self.multiline: + return self.text_input.toPlainText() + else: + return self.text_input.text() + + +class PathInputWidget(QtWidgets.QWidget, InputObject): + default_input_value = "" + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(PathInputWidget, self).__init__(parent_widget) + + self.initial_attributes(input_data, parent, as_widget) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + if not self._as_widget: + self.key = input_data["key"] + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0) + self.label_widget = label_widget + + self.path_input = PathInput(self) + self.setFocusProxy(self.path_input) + layout.addWidget(self.path_input, 1) + + self.path_input.textChanged.connect(self._on_value_change) + + def set_value(self, value): + self.path_input.setText(value) + + def focusOutEvent(self, event): + self.path_input.clear_end_path() + super(PathInput, self).focusOutEvent(event) + + def update_style(self): + if self._as_widget: + if not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) + + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.path_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.path_input.text() + + +class RawJsonInput(QtWidgets.QPlainTextEdit): + tab_length = 4 + + def __init__(self, *args, **kwargs): + super(RawJsonInput, self).__init__(*args, **kwargs) + self.setObjectName("RawJsonInput") + self.setTabStopDistance( + QtGui.QFontMetricsF( + self.font() + ).horizontalAdvance(" ") * self.tab_length + ) + + def sizeHint(self): + document = self.document() + layout = document.documentLayout() + + height = document.documentMargin() + 2 * self.frameWidth() + 1 + block = document.begin() + while block != document.end(): + height += layout.blockBoundingRect(block).height() + block = block.next() + + hint = super(RawJsonInput, self).sizeHint() + hint.setHeight(height) + + return hint + + def set_value(self, value): + if value is NOT_SET: + value = "" + elif not isinstance(value, str): + try: + value = json.dumps(value, indent=4) + except Exception: + value = "" + self.setPlainText(value) + + def json_value(self): + return json.loads(self.toPlainText()) + + def has_invalid_value(self): + try: + self.json_value() + return False + except Exception: + return True + + def resizeEvent(self, event): + self.updateGeometry() + super(RawJsonInput, self).resizeEvent(event) + + +class RawJsonWidget(QtWidgets.QWidget, InputObject): + default_input_value = "{}" + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(RawJsonWidget, self).__init__(parent_widget) + + self.initial_attributes(input_data, parent, as_widget) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + self.text_input = RawJsonInput(self) + self.text_input.setSizePolicy( + QtWidgets.QSizePolicy.Minimum, + QtWidgets.QSizePolicy.MinimumExpanding + ) + + self.setFocusProxy(self.text_input) + + if not self._as_widget: + self.key = input_data["key"] + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget, 0, alignment=QtCore.Qt.AlignTop) + self.label_widget = label_widget + layout.addWidget(self.text_input, 1, alignment=QtCore.Qt.AlignTop) + + self.text_input.textChanged.connect(self._on_value_change) + + def update_studio_values(self, parent_values): + self._is_invalid = self.text_input.has_invalid_value() + return super(RawJsonWidget, self).update_studio_values(parent_values) + + def set_value(self, value): + self.text_input.set_value(value) + + def _on_value_change(self, *args, **kwargs): + self._is_invalid = self.text_input.has_invalid_value() + return super(RawJsonWidget, self)._on_value_change(*args, **kwargs) + + def update_style(self): + if self._as_widget: + if not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) + + if self._state == state: + return + + if self._as_widget: + property_name = "input-state" + widget = self.text_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + if self.is_invalid: + return NOT_SET + return self.text_input.json_value() + + +class ListItem(QtWidgets.QWidget, SettingObject): + _btn_size = 20 + value_changed = QtCore.Signal(object) + + def __init__(self, object_type, input_modifiers, config_parent, parent): + super(ListItem, self).__init__(parent) + + self._set_default_attributes() + + self._parent = config_parent + self._any_parent_is_group = True + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + + char_up = qtawesome.charmap("fa.angle-up") + char_down = qtawesome.charmap("fa.angle-down") + + self.add_btn = QtWidgets.QPushButton("+") + self.remove_btn = QtWidgets.QPushButton("-") + self.up_btn = QtWidgets.QPushButton(char_up) + self.down_btn = QtWidgets.QPushButton(char_down) + + font_up_down = qtawesome.font("fa", 13) + self.up_btn.setFont(font_up_down) + self.down_btn.setFont(font_up_down) + + self.add_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + self.remove_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + self.up_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + self.down_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + + self.add_btn.setFixedSize(self._btn_size, self._btn_size) + self.remove_btn.setFixedSize(self._btn_size, self._btn_size) + self.up_btn.setFixedSize(self._btn_size, self._btn_size) + self.down_btn.setFixedSize(self._btn_size, self._btn_size) + + self.add_btn.setProperty("btn-type", "tool-item") + self.remove_btn.setProperty("btn-type", "tool-item") + self.up_btn.setProperty("btn-type", "tool-item") + self.down_btn.setProperty("btn-type", "tool-item") + + layout.addWidget(self.add_btn, 0) + layout.addWidget(self.remove_btn, 0) + + self.add_btn.clicked.connect(self._on_add_clicked) + self.remove_btn.clicked.connect(self._on_remove_clicked) + self.up_btn.clicked.connect(self._on_up_clicked) + self.down_btn.clicked.connect(self._on_down_clicked) + + ItemKlass = TypeToKlass.types[object_type] + self.value_input = ItemKlass( + input_modifiers, + self, + as_widget=True, + label_widget=None + ) + layout.addWidget(self.value_input, 1) + + layout.addWidget(self.up_btn, 0) + layout.addWidget(self.down_btn, 0) + + self.value_input.value_changed.connect(self._on_value_change) + + def set_as_empty(self, is_empty=True): + self.value_input.setEnabled(not is_empty) + self.remove_btn.setEnabled(not is_empty) + self.order_changed() + self._on_value_change() + + def order_changed(self): + row = self.row() + parent_row_count = self.parent_rows_count() + if parent_row_count == 1: + self.up_btn.setEnabled(False) + self.down_btn.setEnabled(False) + + elif row == 0: + self.up_btn.setEnabled(False) + self.down_btn.setEnabled(True) + + elif row == parent_row_count - 1: + self.up_btn.setEnabled(True) + self.down_btn.setEnabled(False) + + else: + self.up_btn.setEnabled(True) + self.down_btn.setEnabled(True) + + def _on_value_change(self, item=None): + self.value_changed.emit(self) + + def row(self): + return self._parent.input_fields.index(self) + + def parent_rows_count(self): + return len(self._parent.input_fields) + + def _on_add_clicked(self): + if self.value_input.isEnabled(): + self._parent.add_row(row=self.row() + 1) + else: + self.set_as_empty(False) + + def _on_remove_clicked(self): + self._parent.remove_row(self) + + def _on_up_clicked(self): + row = self.row() + self._parent.swap_rows(row - 1, row) + + def _on_down_clicked(self): + row = self.row() + self._parent.swap_rows(row, row + 1) + + def config_value(self): + if self.value_input.isEnabled(): + return self.value_input.item_value() + return NOT_SET + + @property + def child_has_studio_override(self): + return self.value_input.child_has_studio_override + + @property + def child_modified(self): + return self.value_input.child_modified + + @property + def child_overriden(self): + return self.value_input.child_overriden + + def hierarchical_style_update(self): + self.value_input.hierarchical_style_update() + + def mouseReleaseEvent(self, event): + return QtWidgets.QWidget.mouseReleaseEvent(self, event) + + def update_default_values(self, value): + self.value_input.update_default_values(value) + + def update_studio_values(self, value): + self.value_input.update_studio_values(value) + + def apply_overrides(self, value): + self.value_input.apply_overrides(value) + + +class ListWidget(QtWidgets.QWidget, InputObject): + default_input_value = [] + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(ListWidget, self).__init__(parent_widget) + self.setObjectName("ListWidget") + + self.initial_attributes(input_data, parent, as_widget) + + self.object_type = input_data["object_type"] + self.input_modifiers = input_data.get("input_modifiers") or {} + + self.key = input_data["key"] + + self.input_fields = [] + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 5) + layout.setSpacing(5) + + if not label_widget: + label_widget = QtWidgets.QLabel(input_data["label"], self) + layout.addWidget(label_widget, alignment=QtCore.Qt.AlignTop) + + self.label_widget = label_widget + + inputs_widget = QtWidgets.QWidget(self) + inputs_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + layout.addWidget(inputs_widget) + + inputs_layout = QtWidgets.QVBoxLayout(inputs_widget) + inputs_layout.setContentsMargins(0, 0, 0, 0) + inputs_layout.setSpacing(3) + + self.inputs_widget = inputs_widget + self.inputs_layout = inputs_layout + + self.add_row(is_empty=True) + + def count(self): + return len(self.input_fields) + + def update_studio_values(self, parent_values): + super(ListWidget, self).update_studio_values(parent_values) + + self.hierarchical_style_update() + + def set_value(self, value): + previous_inputs = tuple(self.input_fields) + for item_value in value: + self.add_row(value=item_value) + + for input_field in previous_inputs: + self.remove_row(input_field) + + if self.count() == 0: + self.add_row(is_empty=True) + + def swap_rows(self, row_1, row_2): + if row_1 == row_2: + return + + if row_1 > row_2: + row_1, row_2 = row_2, row_1 + + field_1 = self.input_fields[row_1] + field_2 = self.input_fields[row_2] + + self.input_fields[row_1] = field_2 + self.input_fields[row_2] = field_1 + + layout_index = self.inputs_layout.indexOf(field_1) + self.inputs_layout.insertWidget(layout_index + 1, field_1) + + field_1.order_changed() + field_2.order_changed() + + def add_row(self, row=None, value=None, is_empty=False): + # Create new item + item_widget = ListItem( + self.object_type, self.input_modifiers, self, self.inputs_widget + ) + if row is None: + if self.input_fields: + self.input_fields[-1].order_changed() + self.inputs_layout.addWidget(item_widget) + self.input_fields.append(item_widget) + else: + previous_field = None + if row > 0: + previous_field = self.input_fields[row - 1] + + next_field = None + max_index = self.count() + if row < max_index: + next_field = self.input_fields[row] + + self.inputs_layout.insertWidget(row, item_widget) + self.input_fields.insert(row, item_widget) + if previous_field: + previous_field.order_changed() + + if next_field: + next_field.order_changed() + + if is_empty: + item_widget.set_as_empty() + item_widget.value_changed.connect(self._on_value_change) + + item_widget.order_changed() + + previous_input = None + for input_field in self.input_fields: + if previous_input is not None: + self.setTabOrder( + previous_input, input_field.value_input.focusProxy() + ) + previous_input = input_field.value_input.focusProxy() + + # Set text if entered text is not None + # else (when add button clicked) trigger `_on_value_change` + if value is not None: + if self._is_overriden: + item_widget.apply_overrides(value) + elif not self._has_studio_override: + item_widget.update_default_values(value) + else: + item_widget.update_studio_values(value) + self.hierarchical_style_update() + else: + self._on_value_change() + self.updateGeometry() + + def remove_row(self, item_widget): + item_widget.value_changed.disconnect() + + row = self.input_fields.index(item_widget) + previous_field = None + next_field = None + if row > 0: + previous_field = self.input_fields[row - 1] + + if row != len(self.input_fields) - 1: + next_field = self.input_fields[row + 1] + + self.inputs_layout.removeWidget(item_widget) + self.input_fields.pop(row) + item_widget.setParent(None) + item_widget.deleteLater() + + if previous_field: + previous_field.order_changed() + + if next_field: + next_field.order_changed() + + if self.count() == 0: + self.add_row(is_empty=True) + + self._on_value_change() + self.updateGeometry() + + def apply_overrides(self, parent_values): + self._is_modified = False + if parent_values is NOT_SET or self.key not in parent_values: + override_value = NOT_SET + else: + override_value = parent_values[self.key] + + self.override_value = override_value + + if override_value is NOT_SET: + self._is_overriden = False + self._was_overriden = False + if self.has_studio_override: + value = self.studio_value + else: + value = self.default_value + else: + self._is_overriden = True + self._was_overriden = True + value = override_value + + self._is_modified = False + self._state = None + + self.set_value(value) + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + self.update_style() + + def update_style(self): + if self._as_widget: + if not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + False, + self._is_invalid, + False, + self._is_modified + ) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) + if self._state == state: + return + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + def item_value(self): + output = [] + for item in self.input_fields: + value = item.config_value() + if value is not NOT_SET: + output.append(value) + return output + + +class ModifiableDictItem(QtWidgets.QWidget, SettingObject): + _btn_size = 20 + value_changed = QtCore.Signal(object) + + def __init__(self, object_type, input_modifiers, config_parent, parent): + super(ModifiableDictItem, self).__init__(parent) + + self._set_default_attributes() + self._parent = config_parent + + self.is_key_duplicated = False + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + + ItemKlass = TypeToKlass.types[object_type] + + self.key_input = QtWidgets.QLineEdit(self) + self.key_input.setObjectName("DictKey") + + self.value_input = ItemKlass( + input_modifiers, + self, + as_widget=True, + label_widget=None + ) + self.add_btn = QtWidgets.QPushButton("+") + self.remove_btn = QtWidgets.QPushButton("-") + + self.add_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + self.remove_btn.setFocusPolicy(QtCore.Qt.ClickFocus) + + self.add_btn.setProperty("btn-type", "tool-item") + self.remove_btn.setProperty("btn-type", "tool-item") + + layout.addWidget(self.add_btn, 0) + layout.addWidget(self.remove_btn, 0) + layout.addWidget(self.key_input, 0) + layout.addWidget(self.value_input, 1) + + self.setFocusProxy(self.value_input) + + self.add_btn.setFixedSize(self._btn_size, self._btn_size) + self.remove_btn.setFixedSize(self._btn_size, self._btn_size) + self.add_btn.clicked.connect(self.on_add_clicked) + self.remove_btn.clicked.connect(self.on_remove_clicked) + + self.key_input.textChanged.connect(self._on_value_change) + self.value_input.value_changed.connect(self._on_value_change) + + self.origin_key = NOT_SET + + def key_value(self): + return self.key_input.text() + + def _is_enabled(self): + return self.key_input.isEnabled() + + def is_key_invalid(self): + if not self._is_enabled(): + return False + + if self.key_value() == "": + return True + + if self.is_key_duplicated: + return True + return False + + def _on_value_change(self, item=None): + self.update_style() + self.value_changed.emit(self) + + def update_default_values(self, key, value): + self.origin_key = key + self.key_input.setText(key) + self.value_input.update_default_values(value) + + def update_studio_values(self, key, value): + self.origin_key = key + self.key_input.setText(key) + self.value_input.update_studio_values(value) + + def apply_overrides(self, key, value): + self.origin_key = key + self.key_input.setText(key) + self.value_input.apply_overrides(value) + + @property + def is_group(self): + return self._parent.is_group + + def on_add_clicked(self): + if self._is_enabled(): + self._parent.add_row(row=self.row() + 1) + else: + self.set_as_empty(False) + + def on_remove_clicked(self): + self._parent.remove_row(self) + + def set_as_empty(self, is_empty=True): + self.key_input.setEnabled(not is_empty) + self.value_input.setEnabled(not is_empty) + self.remove_btn.setEnabled(not is_empty) + self._on_value_change() + + @property + def any_parent_is_group(self): + return self._parent.any_parent_is_group + + def is_key_modified(self): + return self.key_value() != self.origin_key + + def is_value_modified(self): + return self.value_input.is_modified + + @property + def is_modified(self): + return self.is_value_modified() or self.is_key_modified() + + def hierarchical_style_update(self): + self.value_input.hierarchical_style_update() + self.update_style() + + @property + def is_invalid(self): + if not self._is_enabled(): + return False + return self.is_key_invalid() or self.value_input.is_invalid + + def update_style(self): + state = "" + if self._is_enabled(): + if self.is_key_invalid(): + state = "invalid" + elif self.is_key_modified(): + state = "modified" + + self.key_input.setProperty("state", state) + self.key_input.style().polish(self.key_input) + + def row(self): + return self._parent.input_fields.index(self) + + def item_value(self): + key = self.key_input.text() + value = self.value_input.item_value() + return {key: value} + + def config_value(self): + if self._is_enabled(): + return self.item_value() + return {} + + def mouseReleaseEvent(self, event): + return QtWidgets.QWidget.mouseReleaseEvent(self, event) + + +class ModifiableDict(QtWidgets.QWidget, InputObject): + default_input_value = {} + # Should be used only for dictionary with one datatype as value + # TODO this is actually input field (do not care if is group or not) + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(ModifiableDict, self).__init__(parent_widget) + self.setObjectName("ModifiableDict") + + self.initial_attributes(input_data, parent, as_widget) + + self.input_fields = [] + + self.key = input_data["key"] + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + content_widget = QtWidgets.QWidget(self) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(CHILD_OFFSET, 3, 0, 3) + + if as_widget: + main_layout.addWidget(content_widget) + body_widget = None + else: + body_widget = ExpandingWidget(input_data["label"], self) + main_layout.addWidget(body_widget) + body_widget.set_content_widget(content_widget) + + self.body_widget = body_widget + self.label_widget = body_widget.label_widget + + collapsable = input_data.get("collapsable", True) + if collapsable: + collapsed = input_data.get("collapsed", True) + if not collapsed: + body_widget.toggle_content() + + else: + body_widget.hide_toolbox(hide_content=False) + + self.body_widget = body_widget + self.content_widget = content_widget + self.content_layout = content_layout + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + self.object_type = input_data["object_type"] + self.input_modifiers = input_data.get("input_modifiers") or {} + + self.add_row(is_empty=True) + + def count(self): + return len(self.input_fields) + + def set_value(self, value): + previous_inputs = tuple(self.input_fields) + for item_key, item_value in value.items(): + self.add_row(key=item_key, value=item_value) + + for input_field in previous_inputs: + self.remove_row(input_field) + + if self.count() == 0: + self.add_row(is_empty=True) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + fields_by_keys = collections.defaultdict(list) + for input_field in self.input_fields: + key = input_field.key_value() + fields_by_keys[key].append(input_field) + + for fields in fields_by_keys.values(): + if len(fields) == 1: + field = fields[0] + if field.is_key_duplicated: + field.is_key_duplicated = False + field.update_style() + else: + for field in fields: + field.is_key_duplicated = True + field.update_style() + + if self.is_overidable: + self._is_overriden = True + else: + self._has_studio_override = True + + if self._is_invalid: + self._is_modified = True + elif self._is_overriden: + self._is_modified = self.item_value() != self.override_value + elif self._has_studio_override: + self._is_modified = self.item_value() != self.studio_value + else: + self._is_modified = self.item_value() != self.default_value + + self.update_style() + + self.value_changed.emit(self) + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + self.update_style() + + def update_style(self): + if self._as_widget: + if not self.isEnabled(): + state = self.style_state(False, False, False, False) + else: + state = self.style_state( + False, + self.is_invalid, + False, + self._is_modified + ) + else: + state = self.style_state( + self.has_studio_override, + self.is_invalid, + self.is_overriden, + self.is_modified + ) + if self._state == state: + return + + if state: + child_state = "child-{}".format(state) + else: + child_state = "" + + if self.body_widget: + self.body_widget.side_line_widget.setProperty("state", child_state) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) + + if not self._as_widget: + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state + + def all_item_values(self): + output = {} + for item in self.input_fields: + output.update(item.item_value()) + return output + + def item_value(self): + output = {} + for item in self.input_fields: + output.update(item.config_value()) + return output + + def add_row(self, row=None, key=None, value=None, is_empty=False): + # Create new item + item_widget = ModifiableDictItem( + self.object_type, self.input_modifiers, self, self.content_widget + ) + if is_empty: + item_widget.set_as_empty() + + item_widget.value_changed.connect(self._on_value_change) + + if row is None: + self.content_layout.addWidget(item_widget) + self.input_fields.append(item_widget) + else: + self.content_layout.insertWidget(row, item_widget) + self.input_fields.insert(row, item_widget) + + previous_input = None + for input_field in self.input_fields: + if previous_input is not None: + self.setTabOrder( + previous_input, input_field.key_input + ) + previous_input = input_field.value_input.focusProxy() + self.setTabOrder( + input_field.key_input, previous_input + ) + + # Set value if entered value is not None + # else (when add button clicked) trigger `_on_value_change` + if value is not None and key is not None: + if not self._has_studio_override: + item_widget.update_default_values(key, value) + elif self._is_overriden: + item_widget.apply_overrides(key, value) + else: + item_widget.update_studio_values(key, value) + self.hierarchical_style_update() + else: + self._on_value_change() + self.parent().updateGeometry() + + def remove_row(self, item_widget): + item_widget.value_changed.disconnect() + + self.content_layout.removeWidget(item_widget) + self.input_fields.remove(item_widget) + item_widget.setParent(None) + item_widget.deleteLater() + + if self.count() == 0: + self.add_row(is_empty=True) + + self._on_value_change() + self.parent().updateGeometry() + + @property + def is_invalid(self): + return self._is_invalid or self.child_invalid + + @property + def child_invalid(self): + for input_field in self.input_fields: + if input_field.is_invalid: + return True + return False + + +# Dictionaries +class DictWidget(QtWidgets.QWidget, SettingObject): + value_changed = QtCore.Signal(object) + + def __init__( + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None + ): + if as_widget: + raise TypeError("Can't use \"{}\" as widget item.".format( + self.__class__.__name__ + )) + + if parent_widget is None: + parent_widget = parent + super(DictWidget, self).__init__(parent_widget) + self.setObjectName("DictWidget") + + self.initial_attributes(input_data, parent, as_widget) + + if input_data.get("highlight_content", False): + content_state = "hightlighted" + bottom_margin = 5 + else: + content_state = "" + bottom_margin = 0 + + self.input_fields = [] + + self.key = input_data["key"] + + main_layout = QtWidgets.QHBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + body_widget = ExpandingWidget(input_data["label"], self) + + main_layout.addWidget(body_widget) + + content_widget = QtWidgets.QWidget(body_widget) + content_widget.setObjectName("ContentWidget") + content_widget.setProperty("content_state", content_state) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(CHILD_OFFSET, 5, 0, bottom_margin) + + body_widget.set_content_widget(content_widget) + + self.body_widget = body_widget + self.content_widget = content_widget + self.content_layout = content_layout + + self.label_widget = body_widget.label_widget + + self.checkbox_widget = None + self.checkbox_key = input_data.get("checkbox_key") + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data) + + collapsable = input_data.get("collapsable", True) + if len(self.input_fields) == 1 and self.checkbox_widget: + body_widget.hide_toolbox(hide_content=True) + + elif collapsable: + collapsed = input_data.get("collapsed", True) + if not collapsed: + body_widget.toggle_content() + else: + body_widget.hide_toolbox(hide_content=False) + + def add_children_gui(self, child_configuration): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + + if not klass.is_input_type: + item = klass(child_configuration, self) + self.content_layout.addWidget(item) + return item + + if self.checkbox_key and not self.checkbox_widget: + key = child_configuration.get("key") + if key == self.checkbox_key: + return self._add_checkbox_child(child_configuration) + + item = klass(child_configuration, self) + item.value_changed.connect(self._on_value_change) + self.content_layout.addWidget(item) + + self.input_fields.append(item) + return item + + def _add_checkbox_child(self, child_configuration): + item = BooleanWidget( + child_configuration, self, label_widget=self.label_widget + ) + item.value_changed.connect(self._on_value_change) + + self.body_widget.add_widget_after_label(item) + self.checkbox_widget = item + self.input_fields.append(item) + return item + + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + for input_field in self.input_fields: + input_field.remove_overrides() + + def reset_to_pype_default(self): + for input_field in self.input_fields: + input_field.reset_to_pype_default() + self._has_studio_override = False + + def set_studio_default(self): + for input_field in self.input_fields: + input_field.set_studio_default() + + if self.is_group: + self._has_studio_override = True + + def discard_changes(self): + self._is_overriden = self._was_overriden + self._is_modified = False + + for input_field in self.input_fields: + input_field.discard_changes() + + self._is_modified = self.child_modified + + def set_as_overriden(self): + if self.is_overriden: + return + + if self.is_group: + self._is_overriden = True + return + + for item in self.input_fields: + item.set_as_overriden() + + def update_default_values(self, parent_values): + value = NOT_SET + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + for item in self.input_fields: + item.update_default_values(value) + + def update_studio_values(self, parent_values): + value = NOT_SET + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + self._has_studio_override = False + if self.is_group and value is not NOT_SET: + self._has_studio_override = True + + self._had_studio_override = bool(self._has_studio_override) + + for item in self.input_fields: + item.update_studio_values(value) + + def apply_overrides(self, parent_values): + # Make sure this is set to False + self._state = None + self._child_state = None + + metadata = {} + groups = tuple() + override_values = NOT_SET + if parent_values is not NOT_SET: + metadata = parent_values.get(METADATA_KEY) or metadata + groups = metadata.get("groups") or groups + override_values = parent_values.get(self.key, override_values) + + self._is_overriden = self.key in groups + + for item in self.input_fields: + item.apply_overrides(override_values) + + if not self._is_overriden: + self._is_overriden = ( + self.is_group + and self.is_overidable + and self.child_overriden + ) + self._was_overriden = bool(self._is_overriden) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_group: + if self.is_overidable: + self._is_overriden = True + else: + self._has_studio_override = True + + self.hierarchical_style_update() + + self.value_changed.emit(self) + + self.update_style() + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + self.update_style() + + def update_style(self, is_overriden=None): + child_has_studio_override = self.child_has_studio_override + child_modified = self.child_modified + child_invalid = self.child_invalid + child_state = self.style_state( + child_has_studio_override, + child_invalid, + self.child_overriden, + child_modified + ) + if child_state: + child_state = "child-{}".format(child_state) + + if child_state != self._child_state: + self.body_widget.side_line_widget.setProperty("state", child_state) + self.body_widget.side_line_widget.style().polish( + self.body_widget.side_line_widget + ) + self._child_state = child_state + + state = self.style_state( + self.had_studio_override, + child_invalid, + self.is_overriden, + self.is_modified + ) + if self._state == state: + return + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state + + @property + def is_modified(self): + if self.is_group: + return self._is_modified or self.child_modified + return False + + @property + def child_has_studio_override(self): + for input_field in self.input_fields: + if ( + input_field.has_studio_override + or input_field.child_has_studio_override + ): + return True + return False + + @property + def child_modified(self): + for input_field in self.input_fields: + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.is_overriden or input_field.child_overriden: + return True + return False + + @property + def child_invalid(self): + for input_field in self.input_fields: + if input_field.child_invalid: + return True + return False + + def get_invalid(self): + output = [] + for input_field in self.input_fields: + output.extend(input_field.get_invalid()) + return output + + def item_value(self): + output = {} + for input_field in self.input_fields: + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + def studio_overrides(self): + if not self.has_studio_override and not self.child_has_studio_override: + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.studio_overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(value.keys()) + if groups: + values[METADATA_KEY] = {"groups": groups} + return {self.key: values}, self.is_group + + def overrides(self): + if not self.is_overriden and not self.child_overriden: + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(value.keys()) + if groups: + values[METADATA_KEY] = {"groups": groups} + return {self.key: values}, self.is_group + + +class DictInvisible(QtWidgets.QWidget, SettingObject): + # TODO is not overridable by itself + value_changed = QtCore.Signal(object) + allow_actions = False + + def __init__( + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(DictInvisible, self).__init__(parent_widget) + self.setObjectName("DictInvisible") + + self.initial_attributes(input_data, parent, as_widget) + + if self._is_group: + raise TypeError("DictInvisible can't be marked as group input.") + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + self.input_fields = [] + + self.key = input_data["key"] + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data) + + def add_children_gui(self, child_configuration): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + + if not klass.is_input_type: + item = klass(child_configuration, self) + self.layout().addWidget(item) + return item + + item = klass(child_configuration, self) + self.layout().addWidget(item) + + item.value_changed.connect(self._on_value_change) + + self.input_fields.append(item) + return item + + def update_style(self, *args, **kwargs): + return + + @property + def child_has_studio_override(self): + for input_field in self.input_fields: + if ( + input_field.has_studio_override + or input_field.child_has_studio_override + ): + return True + return False + + @property + def child_modified(self): + for input_field in self.input_fields: + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.is_overriden or input_field.child_overriden: + return True + return False + + @property + def child_invalid(self): + for input_field in self.input_fields: + if input_field.child_invalid: + return True + return False + + def get_invalid(self): + output = [] + for input_field in self.input_fields: + output.extend(input_field.get_invalid()) + return output + + def item_value(self): + output = {} + for input_field in self.input_fields: + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_group: + if self.is_overidable: + self._is_overriden = True + else: + self._has_studio_override = True + self.hierarchical_style_update() + + self.value_changed.emit(self) + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + self.update_style() + + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + for input_field in self.input_fields: + input_field.remove_overrides() + + def reset_to_pype_default(self): + for input_field in self.input_fields: + input_field.reset_to_pype_default() + self._has_studio_override = False + + def set_studio_default(self): + for input_field in self.input_fields: + input_field.set_studio_default() + + if self.is_group: + self._has_studio_override = True + + def discard_changes(self): + self._is_modified = False + self._is_overriden = self._was_overriden + + for input_field in self.input_fields: + input_field.discard_changes() + + self._is_modified = self.child_modified + + def set_as_overriden(self): + if self.is_overriden: + return + + if self.is_group: + self._is_overriden = True + return + + for item in self.input_fields: + item.set_as_overriden() + + def update_default_values(self, parent_values): + value = NOT_SET + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + for item in self.input_fields: + item.update_default_values(value) + + def update_studio_values(self, parent_values): + value = NOT_SET + if parent_values is not NOT_SET: + value = parent_values.get(self.key, NOT_SET) + + for item in self.input_fields: + item.update_studio_values(value) + + def apply_overrides(self, parent_values): + # Make sure this is set to False + self._state = None + self._child_state = None + + metadata = {} + groups = tuple() + override_values = NOT_SET + if parent_values is not NOT_SET: + metadata = parent_values.get(METADATA_KEY) or metadata + groups = metadata.get("groups") or groups + override_values = parent_values.get(self.key, override_values) + + self._is_overriden = self.key in groups + + for item in self.input_fields: + item.apply_overrides(override_values) + + if not self._is_overriden: + self._is_overriden = ( + self.is_group + and self.is_overidable + and self.child_overriden + ) + self._was_overriden = bool(self._is_overriden) + + def studio_overrides(self): + if not self.has_studio_override and not self.child_has_studio_override: + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.studio_overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(value.keys()) + if groups: + values[METADATA_KEY] = {"groups": groups} + return {self.key: values}, self.is_group + + def overrides(self): + if not self.is_overriden and not self.child_overriden: + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(value.keys()) + if groups: + values[METADATA_KEY] = {"groups": groups} + return {self.key: values}, self.is_group + + +class PathWidget(QtWidgets.QWidget, SettingObject): + value_changed = QtCore.Signal(object) + platforms = ("windows", "darwin", "linux") + platform_labels_mapping = { + "windows": "Windows", + "darwin": "MacOS", + "linux": "Linux" + } + + def __init__( + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(PathWidget, self).__init__(parent_widget) + + self.initial_attributes(input_data, parent, as_widget) + + # This is partial input and dictionary input + if not self.any_parent_is_group and not self._as_widget: + self._is_group = True + else: + self._is_group = False + + self.multiplatform = input_data.get("multiplatform", False) + self.multipath = input_data.get("multipath", False) + + self.input_fields = [] + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + if not self._as_widget: + self.key = input_data["key"] + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + layout.addWidget(label_widget, 0, alignment=QtCore.Qt.AlignTop) + self.label_widget = label_widget + + self.content_widget = QtWidgets.QWidget(self) + self.content_layout = QtWidgets.QVBoxLayout(self.content_widget) + self.content_layout.setSpacing(0) + self.content_layout.setContentsMargins(0, 0, 0, 0) + + layout.addWidget(self.content_widget) + + self.create_gui() + + @property + def default_input_value(self): + if self.multipath: + value_type = list + else: + value_type = str + + if self.multiplatform: + return { + platform: value_type() + for platform in self.platforms + } + else: + return value_type() + + def create_gui(self): + if not self.multiplatform and not self.multipath: + input_data = {"key": self.key} + path_input = PathInputWidget( + input_data, self, label_widget=self.label_widget + ) + self.setFocusProxy(path_input) + self.content_layout.addWidget(path_input) + self.input_fields.append(path_input) + path_input.value_changed.connect(self._on_value_change) + return + + input_data_for_list = { + "object_type": "path-input" + } + if not self.multiplatform: + input_data_for_list["key"] = self.key + input_widget = ListWidget( + input_data_for_list, self, label_widget=self.label_widget + ) + self.setFocusProxy(input_widget) + self.content_layout.addWidget(input_widget) + self.input_fields.append(input_widget) + input_widget.value_changed.connect(self._on_value_change) + return + + proxy_widget = QtWidgets.QWidget(self.content_widget) + proxy_layout = QtWidgets.QFormLayout(proxy_widget) + for platform_key in self.platforms: + platform_label = self.platform_labels_mapping[platform_key] + label_widget = QtWidgets.QLabel(platform_label, proxy_widget) + if self.multipath: + input_data_for_list["key"] = platform_key + input_widget = ListWidget( + input_data_for_list, self, label_widget=label_widget + ) + else: + input_data = {"key": platform_key} + input_widget = PathInputWidget( + input_data, self, label_widget=label_widget + ) + proxy_layout.addRow(label_widget, input_widget) + self.input_fields.append(input_widget) + input_widget.value_changed.connect(self._on_value_change) + + self.setFocusProxy(self.input_fields[0]) + self.content_layout.addWidget(proxy_widget) + + def update_default_values(self, parent_values): + self._state = None + self._child_state = None + self._is_modified = False + + value = NOT_SET + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + if not self.multiplatform: + value = parent_values + else: + value = parent_values.get(self.key, NOT_SET) + + if value is NOT_SET: + if self.develop_mode: + if self._as_widget or not self.multiplatform: + value = {self.key: self.default_input_value} + else: + value = self.default_input_value + self.defaults_not_set = True + if value is NOT_SET: + raise NotImplementedError(( + "{} Does not have implemented" + " attribute `default_input_value`" + ).format(self)) + + else: + raise ValueError( + "Default value is not set. This is implementation BUG." + ) + + self.default_value = value + self._has_studio_override = False + self._had_studio_override = False + + if not self.multiplatform: + self.input_fields[0].update_default_values(value) + else: + for input_field in self.input_fields: + input_field.update_default_values(value) + + def update_studio_values(self, parent_values): + self._state = None + self._child_state = None + self._is_modified = False + + value = NOT_SET + if self._as_widget: + value = parent_values + elif parent_values is not NOT_SET: + if not self.multiplatform: + value = parent_values + else: + value = parent_values.get(self.key, NOT_SET) + + self.studio_value = value + if value is not NOT_SET: + self._has_studio_override = True + self._had_studio_override = True + else: + self._has_studio_override = False + self._had_studio_override = False + value = self.default_value + + if not self.multiplatform: + self.input_fields[0].update_studio_values(value) + else: + for input_field in self.input_fields: + input_field.update_studio_values(value) + + def apply_overrides(self, parent_values): + self._is_modified = False + self._state = None + self._child_state = None + + override_values = NOT_SET + if self._as_widget: + override_values = parent_values + elif parent_values is not NOT_SET: + if not self.multiplatform: + override_values = parent_values + else: + override_values = parent_values.get(self.key, NOT_SET) + + self._is_overriden = override_values is not NOT_SET + self._was_overriden = bool(self._is_overriden) + + if not self.multiplatform: + self.input_fields[0].apply_overrides(parent_values) + else: + for input_field in self.input_fields: + input_field.apply_overrides(override_values) + + if not self._is_overriden: + self._is_overriden = ( + self.is_group + and self.is_overidable + and self.child_overriden + ) + self._is_modified = False + self._was_overriden = bool(self._is_overriden) + + def set_value(self, value): + if not self.multiplatform: + self.input_fields[0].set_value(value) + + else: + for input_field in self.input_fields: + _value = value[input_field.key] + input_field.set_value(_value) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + if self.is_overidable: + self._is_overriden = True + else: + self._has_studio_override = True + + if self._is_invalid: + self._is_modified = True + elif self._is_overriden: + self._is_modified = self.item_value() != self.override_value + elif self._has_studio_override: + self._is_modified = self.item_value() != self.studio_value + else: + self._is_modified = self.item_value() != self.default_value + + self.hierarchical_style_update() + + self.value_changed.emit(self) + + def update_style(self, is_overriden=None): + child_has_studio_override = self.child_has_studio_override + child_modified = self.child_modified + child_invalid = self.child_invalid + child_state = self.style_state( + child_has_studio_override, + child_invalid, + self.child_overriden, + child_modified + ) + if child_state: + child_state = "child-{}".format(child_state) + + if child_state != self._child_state: + self.setProperty("state", child_state) + self.style().polish(self) + self._child_state = child_state + + if not self._as_widget: + state = self.style_state( + child_has_studio_override, + child_invalid, + self.is_overriden, + self.is_modified + ) + if self._state == state: + return + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + self._state = state + + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + for input_field in self.input_fields: + input_field.remove_overrides() + + def reset_to_pype_default(self): + for input_field in self.input_fields: + input_field.reset_to_pype_default() + self._has_studio_override = False + + def set_studio_default(self): + for input_field in self.input_fields: + input_field.set_studio_default() + + if self.is_group: + self._has_studio_override = True + + def discard_changes(self): + self._is_modified = False + self._is_overriden = self._was_overriden + + for input_field in self.input_fields: + input_field.discard_changes() + + self._is_modified = self.child_modified + + def set_as_overriden(self): + self._is_overriden = True + + @property + def child_has_studio_override(self): + for input_field in self.input_fields: + if ( + input_field.has_studio_override + or input_field.child_has_studio_override + ): + return True + return False + + @property + def child_modified(self): + for input_field in self.input_fields: + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.child_overriden: + return True + return False + + @property + def child_invalid(self): + for input_field in self.input_fields: + if input_field.child_invalid: + return True + return False + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + self.update_style() + + def item_value(self): + if not self.multiplatform and not self.multipath: + return self.input_fields[0].item_value() + + if not self.multiplatform: + return self.input_fields[0].item_value() + + output = {} + for input_field in self.input_fields: + output.update(input_field.config_value()) + return output + + def studio_overrides(self): + if not self.has_studio_override and not self.child_has_studio_override: + return NOT_SET, False + + value = self.item_value() + if not self.multiplatform: + value = {self.key: value} + return value, self.is_group + + def overrides(self): + if not self.is_overriden and not self.child_overriden: + return NOT_SET, False + + value = self.item_value() + if not self.multiplatform: + value = {self.key: value} + return value, self.is_group + + +# Proxy for form layout +class FormLabel(QtWidgets.QLabel): + def __init__(self, *args, **kwargs): + super(FormLabel, self).__init__(*args, **kwargs) + self.item = None + + +class DictFormWidget(QtWidgets.QWidget, SettingObject): + value_changed = QtCore.Signal(object) + allow_actions = False + + def __init__( + self, input_data, parent, + as_widget=False, label_widget=None, parent_widget=None + ): + if parent_widget is None: + parent_widget = parent + super(DictFormWidget, self).__init__(parent_widget) + + self.initial_attributes(input_data, parent, as_widget) + + self._as_widget = False + self._is_group = False + + self.input_fields = [] + self.content_layout = QtWidgets.QFormLayout(self) + self.content_layout.setContentsMargins(0, 0, 0, 0) + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + def add_children_gui(self, child_configuration): + item_type = child_configuration["type"] + # Pop label to not be set in child + label = child_configuration["label"] + + klass = TypeToKlass.types.get(item_type) + + label_widget = FormLabel(label, self) + + item = klass(child_configuration, self, label_widget=label_widget) + label_widget.item = item + + item.value_changed.connect(self._on_value_change) + self.content_layout.addRow(label_widget, item) + self.input_fields.append(item) + return item + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.RightButton: + position = self.mapFromGlobal(QtGui.QCursor().pos()) + widget = self.childAt(position) + if widget and isinstance(widget, FormLabel): + widget.item.mouseReleaseEvent(event) + event.accept() + return + super(DictFormWidget, self).mouseReleaseEvent(event) + + def apply_overrides(self, parent_values): + for item in self.input_fields: + item.apply_overrides(parent_values) + + def discard_changes(self): + self._is_modified = False + self._is_overriden = self._was_overriden + + for item in self.input_fields: + item.discard_changes() + + self._is_modified = self.child_modified + + def remove_overrides(self): + self._is_overriden = False + self._is_modified = False + for input_field in self.input_fields: + input_field.remove_overrides() + + def reset_to_pype_default(self): + for input_field in self.input_fields: + input_field.reset_to_pype_default() + self._has_studio_override = False + + def set_studio_default(self): + for input_field in self.input_fields: + input_field.set_studio_default() + + if self.is_group: + self._has_studio_override = True + + def set_as_overriden(self): + if self.is_overriden: + return + + if self.is_group: + self._is_overriden = True + return + + for item in self.input_fields: + item.set_as_overriden() + + def update_default_values(self, value): + for item in self.input_fields: + item.update_default_values(value) + + def update_studio_values(self, value): + for item in self.input_fields: + item.update_studio_values(value) + + def _on_value_change(self, item=None): + if self.ignore_value_changes: + return + + self.value_changed.emit(self) + if self.any_parent_is_group: + self.hierarchical_style_update() + + @property + def child_has_studio_override(self): + for input_field in self.input_fields: + if ( + input_field.has_studio_override + or input_field.child_has_studio_override + ): + return True + return False + + @property + def child_modified(self): + for input_field in self.input_fields: + if input_field.child_modified: + return True + return False + + @property + def child_overriden(self): + for input_field in self.input_fields: + if input_field.is_overriden or input_field.child_overriden: + return True + return False + + @property + def child_invalid(self): + for input_field in self.input_fields: + if input_field.child_invalid: + return True + return False + + def get_invalid(self): + output = [] + for input_field in self.input_fields: + output.extend(input_field.get_invalid()) + return output + + def hierarchical_style_update(self): + for input_field in self.input_fields: + input_field.hierarchical_style_update() + + def item_value(self): + output = {} + for input_field in self.input_fields: + # TODO maybe merge instead of update should be used + # NOTE merge is custom function which merges 2 dicts + output.update(input_field.config_value()) + return output + + def config_value(self): + return self.item_value() + + def studio_overrides(self): + if not self.has_studio_override and not self.child_has_studio_override: + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.studio_overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(value.keys()) + if groups: + values[METADATA_KEY] = {"groups": groups} + return values, self.is_group + + def overrides(self): + if not self.is_overriden and not self.child_overriden: + return NOT_SET, False + + values = {} + groups = [] + for input_field in self.input_fields: + value, is_group = input_field.overrides() + if value is not NOT_SET: + values.update(value) + if is_group: + groups.extend(value.keys()) + if groups: + values[METADATA_KEY] = {"groups": groups} + return values, self.is_group + + +class LabelWidget(QtWidgets.QWidget): + is_input_type = False + + def __init__(self, configuration, parent=None): + super(LabelWidget, self).__init__(parent) + self.setObjectName("LabelWidget") + + label = configuration["label"] + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + label_widget = QtWidgets.QLabel(label, self) + layout.addWidget(label_widget) + + +class SplitterWidget(QtWidgets.QWidget): + is_input_type = False + _height = 2 + + def __init__(self, configuration, parent=None): + super(SplitterWidget, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + splitter_item = QtWidgets.QWidget(self) + splitter_item.setObjectName("SplitterItem") + splitter_item.setMinimumHeight(self._height) + splitter_item.setMaximumHeight(self._height) + layout.addWidget(splitter_item) + + +TypeToKlass.types["boolean"] = BooleanWidget +TypeToKlass.types["number"] = NumberWidget +TypeToKlass.types["text"] = TextWidget +TypeToKlass.types["path-input"] = PathInputWidget +TypeToKlass.types["raw-json"] = RawJsonWidget +TypeToKlass.types["list"] = ListWidget +TypeToKlass.types["dict-modifiable"] = ModifiableDict +TypeToKlass.types["dict"] = DictWidget +TypeToKlass.types["dict-invisible"] = DictInvisible +TypeToKlass.types["path-widget"] = PathWidget +TypeToKlass.types["dict-form"] = DictFormWidget + +TypeToKlass.types["label"] = LabelWidget +TypeToKlass.types["splitter"] = SplitterWidget diff --git a/pype/tools/settings/settings/widgets/lib.py b/pype/tools/settings/settings/widgets/lib.py new file mode 100644 index 0000000000..e225d65417 --- /dev/null +++ b/pype/tools/settings/settings/widgets/lib.py @@ -0,0 +1,311 @@ +import os +import json +import copy +from pype.settings.lib import OVERRIDEN_KEY +from queue import Queue + + +# Singleton database of available inputs +class TypeToKlass: + types = {} + + +NOT_SET = type("NOT_SET", (), {"__bool__": lambda obj: False})() +METADATA_KEY = type("METADATA_KEY", (), {}) +OVERRIDE_VERSION = 1 +CHILD_OFFSET = 15 + + +def convert_gui_data_to_overrides(data, first=True): + if not data or not isinstance(data, dict): + return data + + output = {} + if first: + output["__override_version__"] = OVERRIDE_VERSION + + if METADATA_KEY in data: + metadata = data.pop(METADATA_KEY) + for key, value in metadata.items(): + if key == "groups": + output[OVERRIDEN_KEY] = value + else: + KeyError("Unknown metadata key \"{}\"".format(key)) + + for key, value in data.items(): + output[key] = convert_gui_data_to_overrides(value, False) + return output + + +def convert_overrides_to_gui_data(data, first=True): + if not data or not isinstance(data, dict): + return data + + output = {} + if OVERRIDEN_KEY in data: + groups = data.pop(OVERRIDEN_KEY) + if METADATA_KEY not in output: + output[METADATA_KEY] = {} + output[METADATA_KEY]["groups"] = groups + + for key, value in data.items(): + output[key] = convert_overrides_to_gui_data(value, False) + + return output + + +def _fill_inner_schemas(schema_data, schema_collection): + if schema_data["type"] == "schema": + raise ValueError("First item in schema data can't be schema.") + + children = schema_data.get("children") + if not children: + return schema_data + + new_children = [] + for child in children: + if child["type"] != "schema": + new_child = _fill_inner_schemas(child, schema_collection) + new_children.append(new_child) + continue + + for schema_name in child["children"]: + new_child = _fill_inner_schemas( + schema_collection[schema_name], + schema_collection + ) + new_children.append(new_child) + + schema_data["children"] = new_children + return schema_data + + +class SchemaMissingFileInfo(Exception): + 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 SchemeGroupHierarchyBug(Exception): + def __init__(self, invalid): + full_path_keys = [] + for item in invalid: + full_path_keys.append("\"{}\"".format("/".join(item))) + + msg = ( + "Items with attribute \"is_group\" can't have another item with" + " \"is_group\" attribute as child. Error happened for keys: [{}]" + ).format(", ".join(full_path_keys)) + super(SchemeGroupHierarchyBug, self).__init__(msg) + + +class SchemaDuplicatedKeys(Exception): + 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 keys in one hierarchy level. {}" + ).format(" || ".join(items)) + super(SchemaDuplicatedKeys, self).__init__(msg) + + +def file_keys_from_schema(schema_data): + output = [] + item_type = schema_data["type"] + klass = TypeToKlass.types[item_type] + if not klass.is_input_type: + return output + + keys = [] + key = schema_data.get("key") + if key: + keys.append(key) + + for child in schema_data["children"]: + if child.get("is_file"): + _keys = copy.deepcopy(keys) + _keys.append(child["key"]) + output.append(_keys) + continue + + for result in file_keys_from_schema(child): + _keys = copy.deepcopy(keys) + _keys.extend(result) + output.append(_keys) + return output + + +def validate_all_has_ending_file(schema_data, is_top=True): + item_type = schema_data["type"] + klass = TypeToKlass.types[item_type] + if not klass.is_input_type: + return None + + if schema_data.get("is_file"): + return None + + children = schema_data.get("children") + if not children: + return [[schema_data["key"]]] + + invalid = [] + keyless = "key" not in schema_data + for child in children: + result = validate_all_has_ending_file(child, False) + if result is None: + continue + + if keyless: + invalid.extend(result) + else: + for item in result: + new_invalid = [schema_data["key"]] + new_invalid.extend(item) + invalid.append(new_invalid) + + if not invalid: + return None + + if not is_top: + return invalid + + raise SchemaMissingFileInfo(invalid) + + +def validate_is_group_is_unique_in_hierarchy( + schema_data, any_parent_is_group=False, keys=None +): + is_top = keys is None + if keys is None: + keys = [] + + keyless = "key" not in schema_data + + if not keyless: + keys.append(schema_data["key"]) + + invalid = [] + is_group = schema_data.get("is_group") + if is_group and any_parent_is_group: + invalid.append(copy.deepcopy(keys)) + + if is_group: + any_parent_is_group = is_group + + children = schema_data.get("children") + if not children: + return invalid + + for child in children: + result = validate_is_group_is_unique_in_hierarchy( + child, any_parent_is_group, copy.deepcopy(keys) + ) + if not result: + continue + + invalid.extend(result) + + if invalid and is_group and keys not in invalid: + invalid.append(copy.deepcopy(keys)) + + if not is_top: + return invalid + + if invalid: + raise SchemeGroupHierarchyBug(invalid) + + +def validate_keys_are_unique(schema_data, keys=None): + children = schema_data.get("children") + if not children: + return + + is_top = keys is None + if keys is None: + keys = [schema_data["key"]] + else: + keys.append(schema_data["key"]) + + child_queue = Queue() + for child in children: + child_queue.put(child) + + child_inputs = [] + while not child_queue.empty(): + child = child_queue.get() + if "key" not in child: + _children = child.get("children") or [] + for _child in _children: + child_queue.put(_child) + else: + child_inputs.append(child) + + duplicated_keys = set() + child_keys = set() + for child in child_inputs: + key = child["key"] + if key in child_keys: + duplicated_keys.add(key) + else: + child_keys.add(key) + + invalid = {} + if duplicated_keys: + joined_keys = "/".join(keys) + invalid[joined_keys] = duplicated_keys + + for child in child_inputs: + result = validate_keys_are_unique(child, copy.deepcopy(keys)) + if result: + invalid.update(result) + + if not is_top: + return invalid + + if invalid: + raise SchemaDuplicatedKeys(invalid) + + +def validate_schema(schema_data): + validate_all_has_ending_file(schema_data) + validate_is_group_is_unique_in_hierarchy(schema_data) + validate_keys_are_unique(schema_data) + + +def gui_schema(subfolder, main_schema_name): + subfolder, main_schema_name + dirpath = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "gui_schemas", + subfolder + ) + + loaded_schemas = {} + for filename in os.listdir(dirpath): + basename, ext = os.path.splitext(filename) + if ext != ".json": + continue + + filepath = os.path.join(dirpath, filename) + with open(filepath, "r") as json_stream: + schema_data = json.load(json_stream) + loaded_schemas[basename] = schema_data + + main_schema = _fill_inner_schemas( + loaded_schemas[main_schema_name], + loaded_schemas + ) + validate_schema(main_schema) + return main_schema diff --git a/pype/tools/settings/settings/widgets/tests.py b/pype/tools/settings/settings/widgets/tests.py new file mode 100644 index 0000000000..fc53e38ad5 --- /dev/null +++ b/pype/tools/settings/settings/widgets/tests.py @@ -0,0 +1,136 @@ +from Qt import QtWidgets, QtCore + + +def indented_print(data, indent=0): + spaces = " " * (indent * 4) + if not isinstance(data, dict): + print("{}{}".format(spaces, data)) + return + + for key, value in data.items(): + print("{}{}".format(spaces, key)) + indented_print(value, indent + 1) + + +class SelectableMenu(QtWidgets.QMenu): + + selection_changed = QtCore.Signal() + + def mouseReleaseEvent(self, event): + action = self.activeAction() + if action and action.isEnabled(): + action.trigger() + self.selection_changed.emit() + else: + super(SelectableMenu, self).mouseReleaseEvent(event) + + def event(self, event): + result = super(SelectableMenu, self).event(event) + if event.type() == QtCore.QEvent.Show: + parent = self.parent() + + move_point = parent.mapToGlobal(QtCore.QPoint(0, parent.height())) + check_point = ( + move_point + + QtCore.QPoint(self.width(), self.height()) + ) + visibility_check = ( + QtWidgets.QApplication.desktop().rect().contains(check_point) + ) + if not visibility_check: + move_point -= QtCore.QPoint(0, parent.height() + self.height()) + self.move(move_point) + + self.updateGeometry() + self.repaint() + + return result + + +class AddibleComboBox(QtWidgets.QComboBox): + """Searchable ComboBox with empty placeholder value as first value""" + + def __init__(self, placeholder="", parent=None): + super(AddibleComboBox, self).__init__(parent) + + self.setEditable(True) + # self.setInsertPolicy(self.NoInsert) + + self.lineEdit().setPlaceholderText(placeholder) + # self.lineEdit().returnPressed.connect(self.on_return_pressed) + + # Apply completer settings + completer = self.completer() + completer.setCompletionMode(completer.PopupCompletion) + completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive) + + # def on_return_pressed(self): + # text = self.lineEdit().text().strip() + # if not text: + # return + # + # index = self.findText(text) + # if index < 0: + # self.addItems([text]) + # index = self.findText(text) + + def populate(self, items): + self.clear() + # self.addItems([""]) # ensure first item is placeholder + self.addItems(items) + + def get_valid_value(self): + """Return the current text if it's a valid value else None + + Note: The empty placeholder value is valid and returns as "" + + """ + + text = self.currentText() + lookup = set(self.itemText(i) for i in range(self.count())) + if text not in lookup: + return None + + return text or None + + +class MultiselectEnum(QtWidgets.QWidget): + + selection_changed = QtCore.Signal() + + def __init__(self, title, parent=None): + super(MultiselectEnum, self).__init__(parent) + toolbutton = QtWidgets.QToolButton(self) + toolbutton.setText(title) + + toolmenu = SelectableMenu(toolbutton) + + toolbutton.setMenu(toolmenu) + toolbutton.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup) + + layout = QtWidgets.QHBoxLayout() + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(toolbutton) + + self.setLayout(layout) + + toolmenu.selection_changed.connect(self.selection_changed) + + self.toolbutton = toolbutton + self.toolmenu = toolmenu + self.main_layout = layout + + def populate(self, items): + self.toolmenu.clear() + self.addItems(items) + + def addItems(self, items): + for item in items: + action = self.toolmenu.addAction(item) + action.setCheckable(True) + action.setChecked(True) + self.toolmenu.addAction(action) + + def items(self): + for action in self.toolmenu.actions(): + yield action diff --git a/pype/tools/settings/settings/widgets/widgets.py b/pype/tools/settings/settings/widgets/widgets.py new file mode 100644 index 0000000000..400b9371fd --- /dev/null +++ b/pype/tools/settings/settings/widgets/widgets.py @@ -0,0 +1,228 @@ +from Qt import QtWidgets, QtCore, QtGui + + +class NumberSpinBox(QtWidgets.QDoubleSpinBox): + def __init__(self, *args, **kwargs): + min_value = kwargs.pop("minimum", -99999) + max_value = kwargs.pop("maximum", 99999) + decimals = kwargs.pop("decimal", 0) + super(NumberSpinBox, self).__init__(*args, **kwargs) + self.setFocusPolicy(QtCore.Qt.StrongFocus) + self.setDecimals(decimals) + self.setMinimum(min_value) + self.setMaximum(max_value) + + def wheelEvent(self, event): + if self.hasFocus(): + super(NumberSpinBox, self).wheelEvent(event) + else: + event.ignore() + + def value(self): + output = super(NumberSpinBox, self).value() + if self.decimals() == 0: + output = int(output) + return output + + +class PathInput(QtWidgets.QLineEdit): + def clear_end_path(self): + value = self.text().strip() + if value.endswith("/"): + while value and value[-1] == "/": + value = value[:-1] + self.setText(value) + + def keyPressEvent(self, event): + # Always change backslash `\` for forwardslash `/` + if event.key() == QtCore.Qt.Key_Backslash: + event.accept() + new_event = QtGui.QKeyEvent( + event.type(), + QtCore.Qt.Key_Slash, + event.modifiers(), + "/", + event.isAutoRepeat(), + event.count() + ) + QtWidgets.QApplication.sendEvent(self, new_event) + return + super(PathInput, self).keyPressEvent(event) + + def focusOutEvent(self, event): + super(PathInput, self).focusOutEvent(event) + self.clear_end_path() + + +class ClickableWidget(QtWidgets.QWidget): + clicked = QtCore.Signal() + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + self.clicked.emit() + super(ClickableWidget, self).mouseReleaseEvent(event) + + +class ExpandingWidget(QtWidgets.QWidget): + def __init__(self, label, parent): + super(ExpandingWidget, self).__init__(parent) + + self.toolbox_hidden = False + + top_part = ClickableWidget(parent=self) + + button_size = QtCore.QSize(5, 5) + button_toggle = QtWidgets.QToolButton(parent=top_part) + button_toggle.setProperty("btn-type", "expand-toggle") + button_toggle.setIconSize(button_size) + button_toggle.setArrowType(QtCore.Qt.RightArrow) + button_toggle.setCheckable(True) + button_toggle.setChecked(False) + + label_widget = QtWidgets.QLabel(label, parent=top_part) + label_widget.setObjectName("DictLabel") + + side_line_widget = QtWidgets.QWidget(top_part) + side_line_widget.setObjectName("SideLineWidget") + side_line_layout = QtWidgets.QHBoxLayout(side_line_widget) + side_line_layout.setContentsMargins(5, 10, 0, 10) + side_line_layout.addWidget(button_toggle) + side_line_layout.addWidget(label_widget) + + top_part_layout = QtWidgets.QHBoxLayout(top_part) + top_part_layout.setContentsMargins(0, 0, 0, 0) + top_part_layout.addWidget(side_line_widget) + + self.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + self.top_part_ending = None + self.after_label_layout = None + self.end_of_layout = None + + self.side_line_widget = side_line_widget + self.side_line_layout = side_line_layout + self.button_toggle = button_toggle + self.label_widget = label_widget + + top_part.clicked.connect(self._top_part_clicked) + self.button_toggle.clicked.connect(self._btn_clicked) + + self.main_layout = QtWidgets.QVBoxLayout(self) + self.main_layout.setContentsMargins(0, 0, 0, 0) + self.main_layout.setSpacing(0) + self.main_layout.addWidget(top_part) + + def hide_toolbox(self, hide_content=False): + self.button_toggle.setArrowType(QtCore.Qt.NoArrow) + self.toolbox_hidden = True + self.content_widget.setVisible(not hide_content) + self.parent().updateGeometry() + + def set_content_widget(self, content_widget): + content_widget.setVisible(False) + self.main_layout.addWidget(content_widget) + self.content_widget = content_widget + + def _btn_clicked(self): + self.toggle_content(self.button_toggle.isChecked()) + + def _top_part_clicked(self): + self.toggle_content() + + def toggle_content(self, *args): + if self.toolbox_hidden: + return + + if len(args) > 0: + checked = args[0] + else: + checked = not self.button_toggle.isChecked() + arrow_type = QtCore.Qt.RightArrow + if checked: + arrow_type = QtCore.Qt.DownArrow + self.button_toggle.setChecked(checked) + self.button_toggle.setArrowType(arrow_type) + self.content_widget.setVisible(checked) + self.parent().updateGeometry() + + def add_widget_after_label(self, widget): + self._add_side_widget_subwidgets() + self.after_label_layout.addWidget(widget) + + def _add_side_widget_subwidgets(self): + if self.top_part_ending is not None: + return + + top_part_ending = QtWidgets.QWidget(self.side_line_widget) + top_part_ending.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + top_part_ending_layout = QtWidgets.QHBoxLayout(top_part_ending) + top_part_ending_layout.setContentsMargins(0, 0, 0, 0) + top_part_ending_layout.setSpacing(0) + top_part_ending_layout.setAlignment(QtCore.Qt.AlignVCenter) + + after_label_widget = QtWidgets.QWidget(top_part_ending) + spacer_item = QtWidgets.QWidget(top_part_ending) + end_of_widget = QtWidgets.QWidget(top_part_ending) + + self.after_label_layout = QtWidgets.QVBoxLayout(after_label_widget) + self.after_label_layout.setContentsMargins(0, 0, 0, 0) + + self.end_of_layout = QtWidgets.QVBoxLayout(end_of_widget) + self.end_of_layout.setContentsMargins(0, 0, 0, 0) + + spacer_layout = QtWidgets.QVBoxLayout(spacer_item) + spacer_layout.setContentsMargins(0, 0, 0, 0) + + top_part_ending_layout.addWidget(after_label_widget, 0) + top_part_ending_layout.addWidget(spacer_item, 1) + top_part_ending_layout.addWidget(end_of_widget, 0) + + top_part_ending.setAttribute(QtCore.Qt.WA_TranslucentBackground) + after_label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + spacer_item.setAttribute(QtCore.Qt.WA_TranslucentBackground) + end_of_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground) + + self.top_part_ending = top_part_ending + self.side_line_layout.addWidget(top_part_ending) + + def resizeEvent(self, event): + super(ExpandingWidget, self).resizeEvent(event) + self.content_widget.updateGeometry() + + +class UnsavedChangesDialog(QtWidgets.QDialog): + message = "You have unsaved changes. What do you want to do with them?" + + def __init__(self, parent=None): + super().__init__(parent) + message_label = QtWidgets.QLabel(self.message) + + btns_widget = QtWidgets.QWidget(self) + btns_layout = QtWidgets.QHBoxLayout(btns_widget) + + btn_ok = QtWidgets.QPushButton("Save") + btn_ok.clicked.connect(self.on_ok_pressed) + btn_discard = QtWidgets.QPushButton("Discard") + btn_discard.clicked.connect(self.on_discard_pressed) + btn_cancel = QtWidgets.QPushButton("Cancel") + btn_cancel.clicked.connect(self.on_cancel_pressed) + + btns_layout.addWidget(btn_ok) + btns_layout.addWidget(btn_discard) + btns_layout.addWidget(btn_cancel) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(message_label) + layout.addWidget(btns_widget) + + self.state = None + + def on_cancel_pressed(self): + self.done(0) + + def on_ok_pressed(self): + self.done(1) + + def on_discard_pressed(self): + self.done(2) diff --git a/pype/tools/settings/settings/widgets/window.py b/pype/tools/settings/settings/widgets/window.py new file mode 100644 index 0000000000..f83da8efe0 --- /dev/null +++ b/pype/tools/settings/settings/widgets/window.py @@ -0,0 +1,28 @@ +from Qt import QtWidgets +from .base import SystemWidget, ProjectWidget + + +class MainWidget(QtWidgets.QWidget): + widget_width = 1000 + widget_height = 600 + + def __init__(self, develop, parent=None): + super(MainWidget, self).__init__(parent) + self.setObjectName("MainWidget") + self.setWindowTitle("Pype Settings") + + self.resize(self.widget_width, self.widget_height) + + header_tab_widget = QtWidgets.QTabWidget(parent=self) + + studio_widget = SystemWidget(develop, header_tab_widget) + project_widget = ProjectWidget(develop, header_tab_widget) + header_tab_widget.addTab(studio_widget, "System") + header_tab_widget.addTab(project_widget, "Project") + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(0) + layout.addWidget(header_tab_widget) + + self.setLayout(layout)