From 2909b7453e2117282d7181a84fb850824bf86b5f Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Thu, 23 Jul 2020 11:03:44 +0200 Subject: [PATCH] initial commit --- .../kuba_each_case/global/creator.json | 8 + .../kuba_each_case/global/intents.json | 3 + .../project_presets/ftrack/ftrack_config.json | 11 + .../project_presets/global/creator.json | 8 + .../global/slates/example_HD.json | 212 +++ .../config/project_presets/maya/capture.json | 108 ++ .../project_presets/plugins/config.json | 1 + .../plugins/ftrack/publish.json | 6 + .../plugins/global/create.json | 1 + .../plugins/global/filter.json | 1 + .../project_presets/plugins/global/load.json | 1 + .../plugins/global/publish.json | 73 + .../project_presets/plugins/maya/create.json | 1 + .../project_presets/plugins/maya/filter.json | 9 + .../project_presets/plugins/maya/load.json | 18 + .../project_presets/plugins/maya/publish.json | 17 + .../plugins/maya/workfile_build.json | 54 + .../project_presets/plugins/nuke/create.json | 8 + .../project_presets/plugins/nuke/load.json | 1 + .../project_presets/plugins/nuke/publish.json | 48 + .../plugins/nuke/workfile_build.json | 11 + .../plugins/nukestudio/filter.json | 10 + .../plugins/nukestudio/publish.json | 8 + .../plugins/standalonepublisher/publish.json | 17 + .../project_presets/plugins/test/create.json | 8 + .../project_presets/plugins/test/publish.json | 10 + .../premiere/asset_default.json | 5 + .../project_presets/premiere/rules_tasks.json | 21 + .../project_presets/unreal/project_setup.json | 4 + .../studio_presets/ftrack/server_plugins.json | 1 + .../studio_presets/ftrack/user_plugins.json | 5 + .../studio_presets/global/applications.json | 39 + .../global/es/applications.json | 39 + .../config/studio_presets/global/intents.json | 9 + .../studio_presets/global/tray_items.json | 25 + .../muster/templates_mapping.json | 19 + .../applications_gui_schema.json | 153 ++ .../ftrack_projects_gui_schema.json | 30 + .../config_gui_schema/project_gui_schema.json | 13 + .../config_gui_schema/studio_gui_schema.json | 23 + .../config_gui_schema/tools_gui_schema.json | 29 + pype/tools/config_setting/interface.py | 49 + pype/tools/config_setting/style/__init__.py | 12 + pype/tools/config_setting/style/pype_icon.png | Bin 0 -> 3793 bytes pype/tools/config_setting/style/style.css | 90 ++ pype/tools/config_setting/widgets/__init__.py | 6 + pype/tools/config_setting/widgets/base.py | 282 ++++ pype/tools/config_setting/widgets/config.py | 236 +++ pype/tools/config_setting/widgets/inputs.py | 1346 +++++++++++++++++ pype/tools/config_setting/widgets/lib.py | 44 + pype/tools/config_setting/widgets/main.py | 26 + pype/tools/config_setting/widgets/tests.py | 127 ++ 52 files changed, 3286 insertions(+) create mode 100644 pype/tools/config_setting/config/project_overrides/kuba_each_case/global/creator.json create mode 100644 pype/tools/config_setting/config/project_overrides/kuba_each_case/global/intents.json create mode 100644 pype/tools/config_setting/config/project_presets/ftrack/ftrack_config.json create mode 100644 pype/tools/config_setting/config/project_presets/global/creator.json create mode 100644 pype/tools/config_setting/config/project_presets/global/slates/example_HD.json create mode 100644 pype/tools/config_setting/config/project_presets/maya/capture.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/config.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/ftrack/publish.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/global/create.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/global/filter.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/global/load.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/global/publish.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/maya/create.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/maya/filter.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/maya/load.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/maya/publish.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/maya/workfile_build.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/nuke/create.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/nuke/load.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/nuke/publish.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/nuke/workfile_build.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/nukestudio/filter.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/nukestudio/publish.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/standalonepublisher/publish.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/test/create.json create mode 100644 pype/tools/config_setting/config/project_presets/plugins/test/publish.json create mode 100644 pype/tools/config_setting/config/project_presets/premiere/asset_default.json create mode 100644 pype/tools/config_setting/config/project_presets/premiere/rules_tasks.json create mode 100644 pype/tools/config_setting/config/project_presets/unreal/project_setup.json create mode 100644 pype/tools/config_setting/config/studio_presets/ftrack/server_plugins.json create mode 100644 pype/tools/config_setting/config/studio_presets/ftrack/user_plugins.json create mode 100644 pype/tools/config_setting/config/studio_presets/global/applications.json create mode 100644 pype/tools/config_setting/config/studio_presets/global/es/applications.json create mode 100644 pype/tools/config_setting/config/studio_presets/global/intents.json create mode 100644 pype/tools/config_setting/config/studio_presets/global/tray_items.json create mode 100644 pype/tools/config_setting/config/studio_presets/muster/templates_mapping.json create mode 100644 pype/tools/config_setting/config_gui_schema/applications_gui_schema.json create mode 100644 pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json create mode 100644 pype/tools/config_setting/config_gui_schema/project_gui_schema.json create mode 100644 pype/tools/config_setting/config_gui_schema/studio_gui_schema.json create mode 100644 pype/tools/config_setting/config_gui_schema/tools_gui_schema.json create mode 100644 pype/tools/config_setting/interface.py create mode 100644 pype/tools/config_setting/style/__init__.py create mode 100644 pype/tools/config_setting/style/pype_icon.png create mode 100644 pype/tools/config_setting/style/style.css create mode 100644 pype/tools/config_setting/widgets/__init__.py create mode 100644 pype/tools/config_setting/widgets/base.py create mode 100644 pype/tools/config_setting/widgets/config.py create mode 100644 pype/tools/config_setting/widgets/inputs.py create mode 100644 pype/tools/config_setting/widgets/lib.py create mode 100644 pype/tools/config_setting/widgets/main.py create mode 100644 pype/tools/config_setting/widgets/tests.py diff --git a/pype/tools/config_setting/config/project_overrides/kuba_each_case/global/creator.json b/pype/tools/config_setting/config/project_overrides/kuba_each_case/global/creator.json new file mode 100644 index 0000000000..d14e779f01 --- /dev/null +++ b/pype/tools/config_setting/config/project_overrides/kuba_each_case/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/tools/config_setting/config/project_overrides/kuba_each_case/global/intents.json b/pype/tools/config_setting/config/project_overrides/kuba_each_case/global/intents.json new file mode 100644 index 0000000000..bf147c7a19 --- /dev/null +++ b/pype/tools/config_setting/config/project_overrides/kuba_each_case/global/intents.json @@ -0,0 +1,3 @@ +{ + "default": "test" +} diff --git a/pype/tools/config_setting/config/project_presets/ftrack/ftrack_config.json b/pype/tools/config_setting/config/project_presets/ftrack/ftrack_config.json new file mode 100644 index 0000000000..c9dbde4596 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/ftrack/ftrack_config.json @@ -0,0 +1,11 @@ +{ + "status_update": { + "_ignore_": ["in progress", "ommited", "on hold"], + "Ready": ["not ready"], + "In Progress" : ["_any_"] + }, + "status_version_to_task": { + "in progress": "in progress", + "approved": "approved" + } +} diff --git a/pype/tools/config_setting/config/project_presets/global/creator.json b/pype/tools/config_setting/config/project_presets/global/creator.json new file mode 100644 index 0000000000..d14e779f01 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/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/tools/config_setting/config/project_presets/global/slates/example_HD.json b/pype/tools/config_setting/config/project_presets/global/slates/example_HD.json new file mode 100644 index 0000000000..b06391fb63 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/global/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/tools/config_setting/config/project_presets/maya/capture.json b/pype/tools/config_setting/config/project_presets/maya/capture.json new file mode 100644 index 0000000000..b6c4893034 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/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/tools/config_setting/config/project_presets/plugins/config.json b/pype/tools/config_setting/config/project_presets/plugins/config.json new file mode 100644 index 0000000000..9e26dfeeb6 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/config.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/pype/tools/config_setting/config/project_presets/plugins/ftrack/publish.json b/pype/tools/config_setting/config/project_presets/plugins/ftrack/publish.json new file mode 100644 index 0000000000..d0469ae4f7 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/ftrack/publish.json @@ -0,0 +1,6 @@ +{ + "IntegrateFtrackNote": { + "note_with_intent_template": "{intent}: {comment}", + "note_labels": [] + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/global/create.json b/pype/tools/config_setting/config/project_presets/plugins/global/create.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/global/create.json @@ -0,0 +1 @@ +{} diff --git a/pype/tools/config_setting/config/project_presets/plugins/global/filter.json b/pype/tools/config_setting/config/project_presets/plugins/global/filter.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/global/filter.json @@ -0,0 +1 @@ +{} diff --git a/pype/tools/config_setting/config/project_presets/plugins/global/load.json b/pype/tools/config_setting/config/project_presets/plugins/global/load.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/global/load.json @@ -0,0 +1 @@ +{} diff --git a/pype/tools/config_setting/config/project_presets/plugins/global/publish.json b/pype/tools/config_setting/config/project_presets/plugins/global/publish.json new file mode 100644 index 0000000000..6e51e00497 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/global/publish.json @@ -0,0 +1,73 @@ +{ + "IntegrateMasterVersion": { + "enabled": false + }, + "ExtractReview": { + "__documentation__": "http://pype.club/docs/admin_presets_plugins", + "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": { + "options": { + "opacity": 1, + "x_offset": 5, + "y_offset": 5, + "bg_padding": 5, + "bg_opacity": 0.5, + "font_size": 42 + }, + "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"] + } + } + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/maya/create.json b/pype/tools/config_setting/config/project_presets/plugins/maya/create.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/maya/create.json @@ -0,0 +1 @@ +{} diff --git a/pype/tools/config_setting/config/project_presets/plugins/maya/filter.json b/pype/tools/config_setting/config/project_presets/plugins/maya/filter.json new file mode 100644 index 0000000000..83d6f05f31 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/maya/filter.json @@ -0,0 +1,9 @@ +{ + "Preset n1": { + "ValidateNoAnimation": false, + "ValidateShapeDefaultNames": false + }, + "Preset n2": { + "ValidateNoAnimation": false + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/maya/load.json b/pype/tools/config_setting/config/project_presets/plugins/maya/load.json new file mode 100644 index 0000000000..260fbb35ee --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/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/tools/config_setting/config/project_presets/plugins/maya/publish.json b/pype/tools/config_setting/config/project_presets/plugins/maya/publish.json new file mode 100644 index 0000000000..2e2b3164f3 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/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 + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/maya/workfile_build.json b/pype/tools/config_setting/config/project_presets/plugins/maya/workfile_build.json new file mode 100644 index 0000000000..2872b783cb --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/maya/workfile_build.json @@ -0,0 +1,54 @@ +[{ + "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"] + }] +}] diff --git a/pype/tools/config_setting/config/project_presets/plugins/nuke/create.json b/pype/tools/config_setting/config/project_presets/plugins/nuke/create.json new file mode 100644 index 0000000000..4deb0b4ad5 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/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}" + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/nuke/load.json b/pype/tools/config_setting/config/project_presets/plugins/nuke/load.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/nuke/load.json @@ -0,0 +1 @@ +{} diff --git a/pype/tools/config_setting/config/project_presets/plugins/nuke/publish.json b/pype/tools/config_setting/config/project_presets/plugins/nuke/publish.json new file mode 100644 index 0000000000..ab0d0e76a5 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/nuke/publish.json @@ -0,0 +1,48 @@ +{ + "ExtractThumbnail": { + "nodes": { + "Reformat": [ + ["type", "to format"], + ["format", "HD_1080"], + ["filter", "Lanczos6"], + ["black_outside", true], + ["pbb", false] + ] + } + }, + "ValidateNukeWriteKnobs": { + "enabled": false, + "knobs": { + "render": { + "review": true + } + } + }, + "ExtractReviewDataLut": { + "__documentation__": { + "viewer_lut_raw": "set to `true` if you Input_process node on viewer is used with baked screen space, so you have to look with RAW viewer lut. Else just keep it on `false`", + "enabled": "keep in on `false` if you want Nuke baking colorspace workflow applied else FFmpeg will convert imgsequence with baked LUT" + }, + "enabled": false + }, + "ExtractReviewDataMov": { + "__documentation__": { + "viewer_lut_raw": "set to `true` if you Input_process node on viewer is used with baked screen space, so you have to look with RAW viewer lut. Else just keep it on `false`", + "enabled": "keep in on `true` if you want Nuke baking colorspace workflow applied" + }, + "enabled": true, + "viewer_lut_raw": false + }, + "ExtractSlateFrame": { + "__documentation__": { + "viewer_lut_raw": "set to `true` if you Input_process node on viewer is used with baked screen space, so you have to look with RAW viewer lut. Else just keep it on `false`" + }, + "viewer_lut_raw": false + }, + "NukeSubmitDeadline": { + "deadline_priority": 50, + "deadline_pool": "", + "deadline_pool_secondary": "", + "deadline_chunk_size": 1 + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/nuke/workfile_build.json b/pype/tools/config_setting/config/project_presets/plugins/nuke/workfile_build.json new file mode 100644 index 0000000000..d3613c929e --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/nuke/workfile_build.json @@ -0,0 +1,11 @@ +[{ + "tasks": ["compositing"], + + "current_context": [{ + "families": ["render", "plate"], + "repre_names": ["exr" ,"dpx"], + "loaders": ["LoadSequence"] + }], + + "linked_assets": [] +}] diff --git a/pype/tools/config_setting/config/project_presets/plugins/nukestudio/filter.json b/pype/tools/config_setting/config/project_presets/plugins/nukestudio/filter.json new file mode 100644 index 0000000000..bd6a0dc1bd --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/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/tools/config_setting/config/project_presets/plugins/nukestudio/publish.json b/pype/tools/config_setting/config/project_presets/plugins/nukestudio/publish.json new file mode 100644 index 0000000000..8c4ad133f1 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/nukestudio/publish.json @@ -0,0 +1,8 @@ +{ + "CollectInstanceVersion": { + "enabled": false + }, + "ExtractReviewCutUpVideo": { + "tags_addition": [] + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/standalonepublisher/publish.json b/pype/tools/config_setting/config/project_presets/plugins/standalonepublisher/publish.json new file mode 100644 index 0000000000..ecfff12db9 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/plugins/standalonepublisher/publish.json @@ -0,0 +1,17 @@ +{ + "ExtractReviewSP": { + "outputs": { + "h264": { + "input": [ + "-gamma 2.2" + ], + "output": [ + "-pix_fmt yuv420p", + "-crf 18" + ], + "tags": ["preview"], + "ext": "mov" + } + } + } +} diff --git a/pype/tools/config_setting/config/project_presets/plugins/test/create.json b/pype/tools/config_setting/config/project_presets/plugins/test/create.json new file mode 100644 index 0000000000..fa0b2fc05f --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/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/tools/config_setting/config/project_presets/plugins/test/publish.json b/pype/tools/config_setting/config/project_presets/plugins/test/publish.json new file mode 100644 index 0000000000..3180dd5d8a --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/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/tools/config_setting/config/project_presets/premiere/asset_default.json b/pype/tools/config_setting/config/project_presets/premiere/asset_default.json new file mode 100644 index 0000000000..84d2bde3d8 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/premiere/asset_default.json @@ -0,0 +1,5 @@ +{ + "frameStart": 1001, + "handleStart": 0, + "handleEnd": 0 +} diff --git a/pype/tools/config_setting/config/project_presets/premiere/rules_tasks.json b/pype/tools/config_setting/config/project_presets/premiere/rules_tasks.json new file mode 100644 index 0000000000..333c9cd70b --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/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/tools/config_setting/config/project_presets/unreal/project_setup.json b/pype/tools/config_setting/config/project_presets/unreal/project_setup.json new file mode 100644 index 0000000000..8a4dffc526 --- /dev/null +++ b/pype/tools/config_setting/config/project_presets/unreal/project_setup.json @@ -0,0 +1,4 @@ +{ + "dev_mode": false, + "install_unreal_python_engine": false +} diff --git a/pype/tools/config_setting/config/studio_presets/ftrack/server_plugins.json b/pype/tools/config_setting/config/studio_presets/ftrack/server_plugins.json new file mode 100644 index 0000000000..0967ef424b --- /dev/null +++ b/pype/tools/config_setting/config/studio_presets/ftrack/server_plugins.json @@ -0,0 +1 @@ +{} diff --git a/pype/tools/config_setting/config/studio_presets/ftrack/user_plugins.json b/pype/tools/config_setting/config/studio_presets/ftrack/user_plugins.json new file mode 100644 index 0000000000..1ba8e9b511 --- /dev/null +++ b/pype/tools/config_setting/config/studio_presets/ftrack/user_plugins.json @@ -0,0 +1,5 @@ +{ + "TestAction": { + "ignore_me": true + } +} diff --git a/pype/tools/config_setting/config/studio_presets/global/applications.json b/pype/tools/config_setting/config/studio_presets/global/applications.json new file mode 100644 index 0000000000..35e399444c --- /dev/null +++ b/pype/tools/config_setting/config/studio_presets/global/applications.json @@ -0,0 +1,39 @@ +{ + "blender_2.80": true, + "blender_2.81": true, + "blender_2.82": true, + "blender_2.83": true, + "harmony_17": true, + "houdini_16": true, + "houdini_17": true, + "houdini_18": true, + "maya_2016": true, + "maya_2017": true, + "maya_2018": true, + "maya_2019": true, + "maya_2020": true, + "nuke_10.0": true, + "nuke_11.0": true, + "nuke_11.2": true, + "nuke_11.3": true, + "nuke_12.0": true, + "nukex_10.0": true, + "nukex_11.0": true, + "nukex_11.2": true, + "nukex_11.3": true, + "nukex_12.0": true, + "nukestudio_10.0": true, + "nukestudio_11.0": true, + "nukestudio_11.2": true, + "nukestudio_11.3": true, + "nukestudio_12.0": true, + "photoshop_2020": true, + "premiere_2019": true, + "premiere_2020": true, + "python_2": true, + "python_3": true, + "resolve_16": true, + "shell": true, + "storyboardpro_7": true, + "unreal_4.21": true +} diff --git a/pype/tools/config_setting/config/studio_presets/global/es/applications.json b/pype/tools/config_setting/config/studio_presets/global/es/applications.json new file mode 100644 index 0000000000..35e399444c --- /dev/null +++ b/pype/tools/config_setting/config/studio_presets/global/es/applications.json @@ -0,0 +1,39 @@ +{ + "blender_2.80": true, + "blender_2.81": true, + "blender_2.82": true, + "blender_2.83": true, + "harmony_17": true, + "houdini_16": true, + "houdini_17": true, + "houdini_18": true, + "maya_2016": true, + "maya_2017": true, + "maya_2018": true, + "maya_2019": true, + "maya_2020": true, + "nuke_10.0": true, + "nuke_11.0": true, + "nuke_11.2": true, + "nuke_11.3": true, + "nuke_12.0": true, + "nukex_10.0": true, + "nukex_11.0": true, + "nukex_11.2": true, + "nukex_11.3": true, + "nukex_12.0": true, + "nukestudio_10.0": true, + "nukestudio_11.0": true, + "nukestudio_11.2": true, + "nukestudio_11.3": true, + "nukestudio_12.0": true, + "photoshop_2020": true, + "premiere_2019": true, + "premiere_2020": true, + "python_2": true, + "python_3": true, + "resolve_16": true, + "shell": true, + "storyboardpro_7": true, + "unreal_4.21": true +} diff --git a/pype/tools/config_setting/config/studio_presets/global/intents.json b/pype/tools/config_setting/config/studio_presets/global/intents.json new file mode 100644 index 0000000000..c853e27b7f --- /dev/null +++ b/pype/tools/config_setting/config/studio_presets/global/intents.json @@ -0,0 +1,9 @@ +{ + "default": "wip", + "items": { + "": "", + "wip": "WIP", + "test": "TEST", + "final": "FINAL" + } +} diff --git a/pype/tools/config_setting/config/studio_presets/global/tray_items.json b/pype/tools/config_setting/config/studio_presets/global/tray_items.json new file mode 100644 index 0000000000..a42bf67c38 --- /dev/null +++ b/pype/tools/config_setting/config/studio_presets/global/tray_items.json @@ -0,0 +1,25 @@ +{ + "usage": { + "User settings": false, + "Ftrack": false, + "Muster": false, + "Avalon": true, + "Clockify": false, + "Standalone Publish": true, + "Logging": true, + "Idle Manager": true, + "Timers Manager": true, + "Rest Api": true, + "Premiere Communicator": true + }, + "attributes": { + "Rest Api": { + "default_port": 8021, + "exclude_ports": [] + }, + "Timers Manager": { + "full_time": 15, + "message_time": 0.5 + } + } +} diff --git a/pype/tools/config_setting/config/studio_presets/muster/templates_mapping.json b/pype/tools/config_setting/config/studio_presets/muster/templates_mapping.json new file mode 100644 index 0000000000..4edab9077d --- /dev/null +++ b/pype/tools/config_setting/config/studio_presets/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/tools/config_setting/config_gui_schema/applications_gui_schema.json b/pype/tools/config_setting/config_gui_schema/applications_gui_schema.json new file mode 100644 index 0000000000..096964b5b8 --- /dev/null +++ b/pype/tools/config_setting/config_gui_schema/applications_gui_schema.json @@ -0,0 +1,153 @@ +{ + "key": "applications", + "type": "dict-expanding", + "label": "Applications", + "children": [ + { + "type": "dict-form", + "children": [ + { + "key": "blender_2.80", + "type": "boolean", + "label": "Blender 2.80" + }, { + "key": "blender_2.81", + "type": "boolean", + "label": "Blender 2.81" + }, { + "key": "blender_2.82", + "type": "boolean", + "label": "Blender 2.82" + }, { + "key": "blender_2.83", + "type": "boolean", + "label": "Blender 2.83" + }, { + "key": "celaction_local", + "type": "boolean", + "label": "Celaction Local" + }, { + "key": "celaction_remote", + "type": "boolean", + "label": "Celaction Remote" + }, { + "key": "harmony_17", + "type": "boolean", + "label": "Harmony 17" + }, { + "key": "houdini_16", + "type": "boolean", + "label": "Houdini 16" + }, { + "key": "houdini_17", + "type": "boolean", + "label": "Houdini 17" + }, { + "key": "houdini_18", + "type": "boolean", + "label": "Houdini 18" + }, { + "key": "maya_2017", + "type": "boolean", + "label": "Autodest Maya 2017" + }, { + "key": "maya_2018", + "type": "boolean", + "label": "Autodest Maya 2018" + }, { + "key": "maya_2019", + "type": "boolean", + "label": "Autodest Maya 2019" + }, { + "key": "maya_2020", + "type": "boolean", + "label": "Autodest Maya 2020" + }, { + "key": "nuke_10.0", + "type": "boolean", + "label": "Nuke 10.0" + }, { + "key": "nuke_11.2", + "type": "boolean", + "label": "Nuke 11.2" + }, { + "key": "nuke_11.3", + "type": "boolean", + "label": "Nuke 11.3" + }, { + "key": "nuke_12.0", + "type": "boolean", + "label": "Nuke 12.0" + }, { + "key": "nukex_10.0", + "type": "boolean", + "label": "NukeX 10.0" + }, { + "key": "nukex_11.2", + "type": "boolean", + "label": "NukeX 11.2" + }, { + "key": "nukex_11.3", + "type": "boolean", + "label": "NukeX 11.3" + }, { + "key": "nukex_12.0", + "type": "boolean", + "label": "NukeX 12.0" + }, { + "key": "nukestudio_10.0", + "type": "boolean", + "label": "NukeStudio 10.0" + }, { + "key": "nukestudio_11.2", + "type": "boolean", + "label": "NukeStudio 11.2" + }, { + "key": "nukestudio_11.3", + "type": "boolean", + "label": "NukeStudio 11.3" + }, { + "key": "nukestudio_12.0", + "type": "boolean", + "label": "NukeStudio 12.0" + }, { + "key": "houdini_16.5", + "type": "boolean", + "label": "Houdini 16.5" + }, { + "key": "houdini_17", + "type": "boolean", + "label": "Houdini 17" + }, { + "key": "houdini_18", + "type": "boolean", + "label": "Houdini 18" + }, { + "key": "premiere_2019", + "type": "boolean", + "label": "Premiere 2019" + }, { + "key": "premiere_2020", + "type": "boolean", + "label": "Premiere 2020" + }, { + "key": "premiere_2020", + "type": "boolean", + "label": "Premiere 2020" + }, { + "key": "resolve_16", + "type": "boolean", + "label": "BM DaVinci Resolve 16" + }, { + "key": "storyboardpro_7", + "type": "boolean", + "label": "Storyboard Pro 7" + }, { + "key": "unreal_4.24", + "type": "boolean", + "label": "Unreal Editor 4.24" + } + ] + } + ] +} diff --git a/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json b/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json new file mode 100644 index 0000000000..febf84eb4a --- /dev/null +++ b/pype/tools/config_setting/config_gui_schema/ftrack_projects_gui_schema.json @@ -0,0 +1,30 @@ +{ + "key": "ftrack", + "type": "dict-expanding", + "label": "Ftrack", + "children": [ + { + "key": "status_update", + "type": "dict-expanding", + "label": "Status updates", + "children": [ + { + "key": "Ready", + "type": "text-singleline", + "label": "Ready" + } + ] + }, { + "key": "status_version_to_task", + "type": "dict-expanding", + "label": "Version status to Task status", + "children": [ + { + "key": "in progress", + "type": "text-singleline", + "label": "In Progress" + } + ] + } + ] +} diff --git a/pype/tools/config_setting/config_gui_schema/project_gui_schema.json b/pype/tools/config_setting/config_gui_schema/project_gui_schema.json new file mode 100644 index 0000000000..38c07ec33d --- /dev/null +++ b/pype/tools/config_setting/config_gui_schema/project_gui_schema.json @@ -0,0 +1,13 @@ +{ + "key": "studio", + "type": "dict-invisible", + "label": "Studio", + "children": [ + { + "type": "schema", + "children": [ + "ftrack_projects_gui_schema" + ] + } + ] +} diff --git a/pype/tools/config_setting/config_gui_schema/studio_gui_schema.json b/pype/tools/config_setting/config_gui_schema/studio_gui_schema.json new file mode 100644 index 0000000000..7d902bb8db --- /dev/null +++ b/pype/tools/config_setting/config_gui_schema/studio_gui_schema.json @@ -0,0 +1,23 @@ +{ + "key": "studio", + "type": "dict-invisible", + "label": "Studio", + "children": [ + { + "type": "schema", + "children": [ + "applications_gui_schema", + "tools_gui_schema" + ] + }, { + "key": "muster", + "type": "dict-invisible", + "children": [{ + "key": "templates_mapping", + "label": "Muster", + "type": "dict-modifiable", + "object_type": "int" + }] + } + ] +} diff --git a/pype/tools/config_setting/config_gui_schema/tools_gui_schema.json b/pype/tools/config_setting/config_gui_schema/tools_gui_schema.json new file mode 100644 index 0000000000..2f46ef0ec5 --- /dev/null +++ b/pype/tools/config_setting/config_gui_schema/tools_gui_schema.json @@ -0,0 +1,29 @@ +{ + "key": "tools", + "type": "dict-expanding", + "label": "Tools", + "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/config_setting/interface.py b/pype/tools/config_setting/interface.py new file mode 100644 index 0000000000..e95f3f5fe3 --- /dev/null +++ b/pype/tools/config_setting/interface.py @@ -0,0 +1,49 @@ +import os +import sys +os.environ["PYPE_CONFIG"] = ( + "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/pype-config" +) +os.environ["AVALON_MONGO"] = "mongodb://localhost:2707" +sys_paths = ( + "C:/Users/Public/pype_env2/Lib/site-packages", + "C:/Users/jakub.trllo/Desktop/pype/pype-setup", + "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/pype", + "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/avalon-core", + "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/pyblish-base", + "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/pyblish-lite", + "C:/Users/jakub.trllo/Desktop/pype/pype-setup/repos/pype-config" +) +for path in sys_paths: + sys.path.append(path) + +from widgets import main +import style +from Qt import QtWidgets, QtGui + + +class MyApp(QtWidgets.QApplication): + def __init__(self, *args, **kwargs): + super(MyApp, self).__init__(*args, **kwargs) + stylesheet = style.load_stylesheet() + self.setStyleSheet(stylesheet) + self.setWindowIcon(QtGui.QIcon(style.app_icon_path())) + + +if __name__ == "__main__": + app = MyApp(sys.argv) + + # main_widget = QtWidgets.QWidget() + # main_widget.setWindowIcon(QtGui.QIcon(style.app_icon_path())) + # + # layout = QtWidgets.QVBoxLayout(main_widget) + # + # widget = main.MainWidget(main_widget) + + # layout.addWidget(widget) + # main_widget.setLayout(layout) + # main_widget.show() + + widget = main.MainWidget() + widget.show() + + sys.exit(app.exec_()) diff --git a/pype/tools/config_setting/style/__init__.py b/pype/tools/config_setting/style/__init__.py new file mode 100644 index 0000000000..a8f202d97b --- /dev/null +++ b/pype/tools/config_setting/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/config_setting/style/pype_icon.png b/pype/tools/config_setting/style/pype_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bfacf6eeedc753db0d77e661843a78b0eb13ce38 GIT binary patch literal 3793 zcmai1XH=6*x6Ye{CK8MY2ok{)4xlKAASI|oK|&G1NG}mf1QaAla)1M9Vx@*AARG=| zP(e_zUxodrEz3*Bxd-m+vduBh+o{4ukPEgpO zz5xbD!#x!nHCEY!;)_rS{8J6d7GObJpmL-tP0Zl4m(l^NyV<{AlB#8n@>Oo3}NptV#uE zJ|o&Vn3{fQzV*eT>weC;f2IfKUk*oieHjXWt$u9R&iap152)_*D{Q9Z=F!{JIe)SO z%hR$_iz|#7@mk8$pHYcwq2D{bGrQIE=_ z%HJG!GSG{;`1R9ojdwK+=dxS9yRD=@oIg!qCpD2PwV&R3%+AYq-B-Rk#EpqRn42$e zH>EOb!%a~%M3rp~*r<3t>D^S3rWvB9_(;Q~?$xZM(Ovs0MtPn)j$hk-{6skM;4UMb zKV5Cp+5JBoWB+s3dgcu6mFk83jh`Q>2K%`5nIv^tH|c2{{two-)KQXy*m^}ZwzhO} z%KAMn&oAq#2$dI#=U!LuIbuc3tlO`0ov#$=N&j&1zR#|usfdEl(Xv&o9)7Q9wlB0w z%Em%{ivvE5@OL~0hayL@^9qN-46y4z6nW3;4;XCYqP@*w*T}r-!K)%N&5!#{x9*){ zhzXWASaS!qGo?GY?l$su#|0v%Q0;A5E;A>?*mVC%wJDSL=4$tivf}O;AJ@fXsLA== zEOKfsx|h|o`;*Y?jJ=BV+1DNTEg$G>Jk1H*o>$A!jmun%;blqQN=!%I)jzUkms>fp zv0Vq3qr0T+)3yK0Cug|}C!j?6ZI{>BHdqu8$!bfOwI$r&7OTOiU<}94bfcG`6vVJv zxW95=t({(PROyoy zmEu2x|DjmG9bO;u*B6;_lEHeI0b@M^LB{T+rwkh{HhHXD-M&0Q%aJluxD^0*=x^lZ zVPe>A3EYyi#IYPY@i|(&iE@>cojpdA3M=0G+f)}(&^0+{ILA7mgDX?ifc8`jm%MZrr3+I7ne?oFo!pC3p>e4AQQ5%$dRX0Q zDvz@Blmiu6I)2b38^H%lO$qNl8<(~FIZO1vNXqU?M0q(f=?llI@4fV|+krdx$Q<62 zwoi~4<(Iz|Q=_5*1+h;uw#~B+?7)>?WYVA3IC0A7SQ-m{FELP1jafHaF6c2(qJ_3j z&N-K0StGJ`?9SV4d}r^ z4_ldzCUok@j0sxQ32JjCkonCfpzda3wVq8Tu?^;habD(d;>s0$oWBZ_-rL|f&suE| z#sygbVLpOwfji?3#K|vgWsbF@Q(0l|r0f72!ZBczHJ>G<2Z6~E263pnx3F5YiF;rV zH)Ect6YyU@7~05Gz6DxivUZL$t89amWaiyw(5JyEm#EF!z-WCz3hM4TKPr8ENm;lJZOUK(TQ+b6EU1GWUTb_^}rU5-W?M%i0#!NwN<)U4-ZBIhkHHZDu zyl@@67el~bUu@(-@uDwu>B&+d-6m3}@bzV6S*;lGtAU`i zJ$cHm=J3u&RwC<;#gRU6lZnG?|zw#X3(val5R>Nk@EOgPg9i1=CLe{XNtf z1j&QkVpeSZ5s+-fin_=c1Rsz3at*8Hw6v*HjRO$q2X-@a&8%}j{V;n=-DUz_efD%N zRA>2%VX39loVUG~luQA8{aL1nnDFrWMxnm*_EN=YEjDLxEFV^i13^C=sojV0C6bTM zS{<&FKZNP7S<%g%_~~c?6Xejd_B`C?uoCzaF;({R_HYr`qoOi$@4jHbxL;WEVqa?WX%sA)p^LJ7yVkG78CtK> zrC*~p7}VI8Z3&$`v_nCok=`B)0^a*FILT3PIIZ9m)5DRazfS;K{Rf?zl?O_~|F7T^ z|BMO{wjt7jvi&jSdG#G6>fXWK?!S$)>=H^Imkr!GN|Wr?k{Lq!BJ=irs&2g8Q84%y ze@sPC%L%b&sU=_w4{uxR{rS7VLC`DJ(3=8X-HU6;<&T{geBY6WiFCqW&*YC;a2k6C zaw5|p^MRcl8fS@Z&=vA+^42o4vyS0G38778fA+jBw z&f&JAlMDspaO52}+u)SVyxK-xn-?%H!pGLxF|A8c&8G~mSXQ`oxS#3b^9SyudzAYp+{&24XX|s-CR!gj&4W0 zJiL-Nu7Hn?Kv53=DqQKt-&?=u3z=%{@~!{`lTM`Sh!TWxYI&&OB&H`wlP!K*Fnezg znOedfcz+cm#=HMLPr%!Q>%u*b8V?g5d6blGn8;iw?h7Og<}p3I81Xg?c_&wUxXQAp z{SN~E!Iv>akVAcN1Q3!ArIz*npD=A1=b45r>@AI{n2r5K_zs=cJV;(heg&ilG=MqM zbFi?J;nm#Z53;9$d-MKO4t(iX<G)L3gY|#He!w|;Fp?XY~aoINns|b10Kzw&Kh|%eN&b ziL-@qNbT4S>P2k{D-1=L=DW!`nZQGQ%GwTYVo+7abkR#q!7>xD$ik;F!hd;6fVT#2 b;3u>qRjE!lxug$XMKJrL$8D;P_+R@UX<7IA literal 0 HcmV?d00001 diff --git a/pype/tools/config_setting/style/style.css b/pype/tools/config_setting/style/style.css new file mode 100644 index 0000000000..f559a4c3b3 --- /dev/null +++ b/pype/tools/config_setting/style/style.css @@ -0,0 +1,90 @@ +QWidget { + color: #bfccd6; + background-color: #293742; + font-size: 12px; +} + +QCheckBox::indicator { +} +QCheckBox::indicator:focus { + color: #ff0000; +} + +QLineEdit, QSpinBox, QDoubleSpinBox, QPlainTextEdit { + border: 1px solid #aaaaaa; + border-radius: 3px; +} + +QLineEdit:focus, QSpinBox:focus, QDoubleSpinBox:focus, QPlainTextEdit:focus { + border: 1px solid #ffffff; +} + +QLabel[state="original"] {} + +QLabel[state="modified"] { + color: #137cbd; +} + +QLabel[state="overriden-modified"] { + color: #00b386; +} + +QLabel[state="overriden"] { + color: #ff8c1a; +} + +QWidget[input-state="original"] {} +QWidget[input-state="modified"] { + border-color: #137cbd; +} + +QWidget[input-state="overriden-modified"] { + border-color: #00b386; +} + +QWidget[input-state="overriden"] { + border-color: #ff8c1a; +} + +QPushButton[btn-type="text-list"] { + border: 1px solid #bfccd6; + border-radius: 10px; +} + +QPushButton[btn-type="text-list"]:hover { + border-color: #137cbd; + color: #137cbd; +} + +QPushButton[btn-type="expand-toggle"] { + background: transparent; +} + +#DictKey[state="original"] {} + +#DictKey[state="modified"] { + border-color: #137cbd; +} + +#DictKey[state="overriden"] { + border-color: #00f; +} +#DictKey[state="overriden-modified"] { + border-color: #0f0; +} + +#ExpandLabel { + background: transparent; +} + +#DictExpandWidget, #ModifiableDict, #ExpandingWidget { + border: 1px solid #455c6e; + border-radius: 3px; + background: #1d272f; +} + +#TextListSubWidget { + border: 1px solid #455c6e; + border-radius: 3px; + background: #1d272f; +} diff --git a/pype/tools/config_setting/widgets/__init__.py b/pype/tools/config_setting/widgets/__init__.py new file mode 100644 index 0000000000..b295759a36 --- /dev/null +++ b/pype/tools/config_setting/widgets/__init__.py @@ -0,0 +1,6 @@ +from .lib import CustomNone, NOT_SET + + +from .base import * +from .main import * +from .inputs import * diff --git a/pype/tools/config_setting/widgets/base.py b/pype/tools/config_setting/widgets/base.py new file mode 100644 index 0000000000..0cc64a66de --- /dev/null +++ b/pype/tools/config_setting/widgets/base.py @@ -0,0 +1,282 @@ +import os +import json +from Qt import QtWidgets, QtCore, QtGui +from . import config +from .lib import NOT_SET +from avalon import io + + +class TypeToKlass: + types = {} + + +class ClickableWidget(QtWidgets.QLabel): + clicked = QtCore.Signal() + + def __init__(self, *args, **kwargs): + super(ClickableWidget, self).__init__(*args, **kwargs) + self.setObjectName("ExpandLabel") + + def mouseReleaseEvent(self, event): + if event.button() == QtCore.Qt.LeftButton: + self.clicked.emit() + super(ClickableWidget, self).mouseReleaseEvent(event) + + +class PypeConfigurationWidget: + is_category = False + is_overriden = False + is_modified = False + + def config_value(self): + raise NotImplementedError( + "Method `config_value` is not implemented for `{}`.".format( + self.__class__.__name__ + ) + ) + + def value_from_values(self, values, keys=None): + if not values: + return NOT_SET + + if keys is None: + keys = self.keys + + value = values + for key in keys: + if not isinstance(value, dict): + raise TypeError( + "Expected dictionary got {}.".format(str(type(value))) + ) + + if key not in value: + return NOT_SET + value = value[key] + return value + + def add_children_gui(self, child_configuration, values): + raise NotImplementedError(( + "Method `add_children_gui` is not implemented for `{}`." + ).format(self.__class__.__name__)) + + +class StudioWidget(QtWidgets.QWidget, PypeConfigurationWidget): + config_dir = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "config_gui_schema" + ) + is_overidable = False + + def __init__(self, parent=None): + super(StudioWidget, self).__init__(parent) + + self.input_fields = [] + + scroll_widget = QtWidgets.QScrollArea(self) + content_widget = QtWidgets.QWidget(scroll_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.setSpacing(0) + content_layout.setAlignment(QtCore.Qt.AlignTop) + content_widget.setLayout(content_layout) + + # scroll_widget.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) + # scroll_widget.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOn) + scroll_widget.setWidgetResizable(True) + scroll_widget.setWidget(content_widget) + + self.scroll_widget = scroll_widget + self.content_layout = content_layout + self.content_widget = content_widget + + values = {"studio": config.studio_presets()} + schema = config.gui_schema("studio_gui_schema") + self.keys = schema.get("keys", []) + self.add_children_gui(schema, values) + + footer_widget = QtWidgets.QWidget() + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + + btn = QtWidgets.QPushButton("Finish") + spacer_widget = QtWidgets.QWidget() + footer_layout.addWidget(spacer_widget, 1) + footer_layout.addWidget(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) + + btn.clicked.connect(self.___finish) + + def ___finish(self): + output = {} + for item in self.input_fields: + output.update(item.config_value()) + + for key in reversed(self.keys): + _output = {key: output} + output = _output + + print(json.dumps(output, indent=4)) + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + item = klass( + child_configuration, values, self.keys, self + ) + self.input_fields.append(item) + self.content_layout.addWidget(item) + + +class ProjectListWidget(QtWidgets.QWidget): + default = "< Default >" + + def __init__(self, parent=None): + super(ProjectListWidget, self).__init__(parent) + + label = QtWidgets.QLabel("Project") + project_list = QtWidgets.QListView(self) + project_list.setModel(QtGui.QStandardItemModel()) + + layout = QtWidgets.QVBoxLayout(self) + # content_margin = 5 + # layout.setContentsMargins( + # content_margin, + # content_margin, + # content_margin, + # content_margin + # ) + # layout.setSpacing(3) + layout.addWidget(label, 0) + layout.addWidget(project_list, 1) + + self.project_list = project_list + + self.refresh() + + def project_name(self): + current_selection = self.project_list.currentText() + if current_selection == self.default: + return None + return current_selection + + 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()): + print(project_doc["name"]) + items.append(project_doc["name"]) + + for item in items: + model.appendRow(QtGui.QStandardItem(item)) + + if not selected_project: + selected_project = self.default + + found_items = model.findItems(selected_project) + if found_items: + index = model.indexFromItem(found_items[0]) + c = QtCore.QItemSelectionModel.SelectionFlag.SelectCurrent + self.project_list.selectionModel().select( + index, c + ) + # self.project_list.selectionModel().setCurrentIndex( + # index, c + # ) + + + +class ProjectWidget(QtWidgets.QWidget, PypeConfigurationWidget): + config_dir = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "config_gui_schema" + ) + is_overidable = True + + def __init__(self, parent=None): + super(ProjectWidget, self).__init__(parent) + + self.input_fields = [] + + scroll_widget = QtWidgets.QScrollArea(self) + content_widget = QtWidgets.QWidget(scroll_widget) + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) + 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() + content_layout.addWidget(project_list_widget) + + self.project_list_widget = project_list_widget + self.scroll_widget = scroll_widget + self.content_layout = content_layout + self.content_widget = content_widget + + values = config.project_presets() + schema = config.gui_schema("project_gui_schema") + self.keys = schema.get("keys", []) + self.add_children_gui(schema, values) + + footer_widget = QtWidgets.QWidget() + footer_layout = QtWidgets.QHBoxLayout(footer_widget) + + btn = QtWidgets.QPushButton("Finish") + spacer_widget = QtWidgets.QWidget() + footer_layout.addWidget(spacer_widget, 1) + footer_layout.addWidget(btn, 0) + + presets_widget = QtWidgets.QWidget() + presets_layout = QtWidgets.QVBoxLayout(presets_widget) + presets_layout.setContentsMargins(0, 0, 0, 0) + presets_layout.setSpacing(0) + + presets_layout.addWidget(scroll_widget, 1) + presets_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(presets_widget, 1) + + btn.clicked.connect(self.___finish) + + def ___finish(self): + output = {} + for item in self.input_fields: + output.update(item.config_value()) + + for key in reversed(self.keys): + _output = {key: output} + output = _output + + print(json.dumps(output, indent=4)) + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + + item = klass( + child_configuration, values, self.keys, self + ) + self.input_fields.append(item) + self.content_layout.addWidget(item) diff --git a/pype/tools/config_setting/widgets/config.py b/pype/tools/config_setting/widgets/config.py new file mode 100644 index 0000000000..335299cb2f --- /dev/null +++ b/pype/tools/config_setting/widgets/config.py @@ -0,0 +1,236 @@ +import os +import json +import logging +import copy + +# DEBUG SETUP +os.environ["AVALON_PROJECT"] = "kuba_each_case" +os.environ["PYPE_PROJECT_CONFIGS"] = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "config", + "project_overrides" +) +# + +log = logging.getLogger(__name__) + +config_path = os.path.dirname(os.path.dirname(__file__)) +studio_presets_path = os.path.normpath( + os.path.join(config_path, "config", "studio_presets") +) +project_presets_path = os.path.normpath( + os.path.join(config_path, "config", "project_presets") +) +first_run = False + +OVERRIDE_KEY = "__overriden__" +POP_KEY = "__popkey__" + + +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(",}", "}") + + global first_run + if extra_comma and first_run: + log.error("Extra comma in json file: \"{}\"".format(fpath)) + + # return empty dict if file is empty + if standard_json == "": + if first_run: + log.error("Empty json file: \"{}\"".format(fpath)) + 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 + if not first_run: + 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, with_metadata=False): + key = keys.pop(0) + if not keys: + if with_metadata: + _dict[key] = {"type": "file", "value": value} + else: + _dict[key] = value + return _dict + + if key not in _dict: + if with_metadata: + _dict[key] = {"type": "folder", "value": {}} + else: + _dict[key] = {} + + if with_metadata: + sub_dict = _dict[key]["value"] + else: + sub_dict = _dict[key] + + _value = subkey_merge(sub_dict, value, keys, with_metadata) + if with_metadata: + _dict[key]["value"] = _value + else: + _dict[key] = _value + 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 + + with_metadata = kwargs.get("with_metadata") + 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 + ext_len = len(".json") + + for base, _directories, filenames in os.walk(path): + 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_path = os.path.join(base[base_len:], basename) + # dict_keys = dict_path.split(os.path.sep) + dict_keys = base[base_len:].split(os.path.sep) + [basename] + output = subkey_merge(output, value, dict_keys, with_metadata) + + for sub_key in sub_keys: + output = output[sub_key] + return output + + +def studio_presets(*args, **kwargs): + return load_jsons_from_dir(studio_presets_path, *args, **kwargs) + + +def global_project_presets(**kwargs): + return load_jsons_from_dir(project_presets_path, **kwargs) + + +def studio_presets_with_metadata(*args, **kwargs): + return load_jsons_from_dir(studio_presets_path, *args, **kwargs) + + +def global_project_presets_with_metadata(**kwargs): + return load_jsons_from_dir(project_presets_path, **kwargs) + + +def project_preset_overrides(project_name, **kwargs): + project_configs_path = os.environ.get("PYPE_PROJECT_CONFIGS") + if project_name and project_configs_path: + return load_jsons_from_dir( + os.path.join(project_configs_path, project_name), + **kwargs + ) + return {} + + +def merge_overrides(global_dict, override_dict): + if OVERRIDE_KEY in override_dict: + _override = override_dict.pop(OVERRIDE_KEY) + if _override: + return override_dict + + for key, value in override_dict.items(): + if value == POP_KEY: + global_dict.pop(key) + + elif key == OVERRIDE_KEY: + continue + + elif 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(global_presets, project_overrides): + global_presets = copy.deepcopy(global_presets) + if not project_overrides: + return global_presets + return merge_overrides(global_presets, project_overrides) + + +def project_presets(project_name=None, **kwargs): + global_presets = global_project_presets(**kwargs) + + if not project_name: + project_name = os.environ.get("AVALON_PROJECT") + project_overrides = project_preset_overrides(project_name, **kwargs) + + return apply_overrides(global_presets, project_overrides) + + +def gui_schema(schema_name): + filename = schema_name + ".json" + schema_folder = os.path.join( + os.path.dirname(os.path.dirname(__file__)), + "config_gui_schema", + filename + ) + with open(schema_folder, "r") as json_stream: + schema = json.load(json_stream) + return schema + + +p1 = studio_presets(with_metadata=True) +p2 = studio_presets(with_metadata=False) +print(json.dumps(p1, indent=4)) +print(json.dumps(p2, indent=4)) diff --git a/pype/tools/config_setting/widgets/inputs.py b/pype/tools/config_setting/widgets/inputs.py new file mode 100644 index 0000000000..1ddc27278d --- /dev/null +++ b/pype/tools/config_setting/widgets/inputs.py @@ -0,0 +1,1346 @@ +from Qt import QtWidgets, QtCore, QtGui +from . import config +from .base import PypeConfigurationWidget, TypeToKlass, ClickableWidget +from .lib import NOT_SET, AS_WIDGET + +import json + + +class BooleanWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal() + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._as_widget = values is AS_WIDGET + self._parent = parent + + super(BooleanWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.checkbox = QtWidgets.QCheckBox() + self.checkbox.setAttribute(QtCore.Qt.WA_StyledBackground) + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + label_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + layout.addWidget(label_widget) + + layout.addWidget(self.checkbox) + + if not self._as_widget: + self.label_widget = label_widget + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.checkbox.setChecked(value) + + self.origin_value = self.item_value() + + self.checkbox.stateChanged.connect(self._on_value_change) + + def set_value(self, value, origin_value=False): + self.checkbox.setChecked(value) + if origin_value: + self.origin_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.origin_value) + + def clear_value(self): + self.reset_value() + + @property + def is_overidable(self): + return self._parent.is_overidable + + def _on_value_change(self, value=None): + self.is_modified = self.item_value() != self.origin_value + self.is_overriden = True + + self._update_style() + + self.value_changed.emit() + + def _update_style(self): + if self.is_overidable and self.is_overriden: + if self.is_modified: + state = "overriden-modified" + else: + state = "overriden" + elif self.is_modified: + state = "modified" + else: + state = "original" + + if not 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) + + def item_value(self): + return self.checkbox.isChecked() + + def config_value(self): + return {self.key: self.item_value()} + + +class ModifiedIntSpinBox(QtWidgets.QSpinBox): + def __init__(self, *args, **kwargs): + super(ModifiedIntSpinBox, self).__init__(*args, **kwargs) + self.setFocusPolicy(QtCore.Qt.StrongFocus) + + def wheelEvent(self, event): + if self.hasFocus(): + super(ModifiedIntSpinBox, self).wheelEvent(event) + else: + event.ignore() + + +class ModifiedFloatSpinBox(QtWidgets.QDoubleSpinBox): + def __init__(self, *args, **kwargs): + super(ModifiedFloatSpinBox, self).__init__(*args, **kwargs) + self.setFocusPolicy(QtCore.Qt.StrongFocus) + + def wheelEvent(self, event): + if self.hasFocus(): + super(ModifiedFloatSpinBox, self).wheelEvent(event) + else: + event.ignore() + + +class IntegerWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal() + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + self._as_widget = values is AS_WIDGET + + super(IntegerWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.int_input = ModifiedIntSpinBox() + + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.int_input) + + if not self._as_widget: + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.int_input.setValue(value) + + self.origin_value = self.item_value() + + self.int_input.valueChanged.connect(self._on_value_change) + + @property + def is_overidable(self): + return self._parent.is_overidable + + def set_value(self, value, origin_value=False): + self.int_input.setValue(value) + if origin_value: + self.origin_value = self.item_value() + self._on_value_change() + + def clear_value(self): + self.set_value(0) + + def reset_value(self): + self.set_value(self.origin_value) + + def _on_value_change(self, value=None): + self.is_modified = self.item_value() != self.origin_value + self.is_overriden = True + + self._update_style() + + self.value_changed.emit() + + def _update_style(self): + if self.is_overidable and self.is_overriden: + if self.is_modified: + state = "overriden-modified" + else: + state = "overriden" + elif self.is_modified: + state = "modified" + else: + state = "original" + + if self._as_widget: + property_name = "input-state" + widget = self.int_input + else: + property_name = "state" + widget = self.label_widget + + widget.setProperty(property_name, state) + widget.style().polish(widget) + + def item_value(self): + return self.int_input.value() + + def config_value(self): + return {self.key: self.item_value()} + + +class FloatWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal() + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + self._as_widget = values is AS_WIDGET + + super(FloatWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.float_input = ModifiedFloatSpinBox() + + decimals = input_data.get("decimals", 5) + maximum = input_data.get("maximum") + minimum = input_data.get("minimum") + + self.float_input.setDecimals(decimals) + if maximum is not None: + self.float_input.setMaximum(float(maximum)) + if minimum is not None: + self.float_input.setMinimum(float(minimum)) + + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.float_input) + + if not self._as_widget: + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.float_input.setValue(value) + + self.origin_value = self.item_value() + + self.float_input.valueChanged.connect(self._on_value_change) + + @property + def is_overidable(self): + return self._parent.is_overidable + + def set_value(self, value, origin_value=False): + self.float_input.setValue(value) + if origin_value: + self.origin_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.origin_value) + + def clear_value(self): + self.set_value(0) + + def _on_value_change(self, value=None): + self.is_modified = self.item_value() != self.origin_value + self.is_overriden = True + + self._update_style() + + self.value_changed.emit() + + def _update_style(self): + if not self._as_widget: + if self.is_overidable and self.is_overriden: + if self.is_modified: + state = "overriden-modified" + else: + state = "overriden" + elif self.is_modified: + state = "modified" + else: + state = "original" + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + def item_value(self): + return self.float_input.value() + + def config_value(self): + return {self.key: self.item_value()} + + +class TextSingleLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal() + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + self._as_widget = values is AS_WIDGET + + super(TextSingleLineWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.text_input = QtWidgets.QLineEdit() + + if not self._as_widget and not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.text_input) + + if not self._as_widget: + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.text_input.setText(value) + + self.origin_value = self.item_value() + + self.text_input.textChanged.connect(self._on_value_change) + + @property + def is_overidable(self): + return self._parent.is_overidable + + def set_value(self, value, origin_value=False): + self.text_input.setText(value) + if origin_value: + self.origin_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.origin_value) + + def clear_value(self): + self.set_value("") + + def _on_value_change(self, value=None): + self.is_modified = self.item_value() != self.origin_value + self.is_overriden = True + + self._update_style() + + self.value_changed.emit() + + def _update_style(self): + if self.is_overidable and self.is_overriden: + if self.is_modified: + state = "overriden-modified" + else: + state = "overriden" + elif self.is_modified: + state = "modified" + else: + state = "original" + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + def item_value(self): + return self.text_input.text() + + def config_value(self): + return {self.key: self.item_value()} + + +class TextMultiLineWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal() + + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + + super(TextMultiLineWidget, self).__init__(parent) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + self.text_input = QtWidgets.QPlainTextEdit() + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + layout.addWidget(self.text_input) + + self.label_widget = label_widget + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.text_input.setPlainText(value) + + self.origin_value = self.item_value() + + self.text_input.textChanged.connect(self._on_value_change) + + @property + def is_overidable(self): + return self._parent.is_overidable + + def set_value(self, value, origin_value=False): + self.text_input.setPlainText(value) + if origin_value: + self.origin_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.origin_value) + + def clear_value(self): + self.set_value("") + + def _on_value_change(self, value=None): + self.is_modified = self.item_value() != self.origin_value + self.is_overriden = True + + self._update_style() + + self.value_changed.emit() + + def _update_style(self): + if self.is_overidable and self.is_overriden: + if self.is_modified: + state = "overriden-modified" + else: + state = "overriden" + elif self.is_modified: + state = "modified" + else: + state = "original" + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + def item_value(self): + return self.text_input.toPlainText() + + def config_value(self): + return {self.key: self.item_value()} + + +class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): + _btn_size = 20 + value_changed = QtCore.Signal() + + def __init__(self, parent): + super(TextListItem, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + + self.text_input = QtWidgets.QLineEdit() + self.add_btn = QtWidgets.QPushButton("+") + self.remove_btn = QtWidgets.QPushButton("-") + + self.add_btn.setProperty("btn-type", "text-list") + self.remove_btn.setProperty("btn-type", "text-list") + + layout.addWidget(self.text_input, 1) + layout.addWidget(self.add_btn, 0) + layout.addWidget(self.remove_btn, 0) + + 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.text_input.textChanged.connect(self._on_value_change) + + self.is_single = False + + def _on_value_change(self): + self.value_changed.emit() + + def row(self): + return self.parent().input_fields.index(self) + + def on_add_clicked(self): + self.parent().add_row(row=self.row() + 1) + + def on_remove_clicked(self): + if self.is_single: + self.text_input.setText("") + else: + self.parent().remove_row(self) + + def config_value(self): + return self.text_input.text() + + +class TextListSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal() + + def __init__(self, input_data, values, parent_keys, parent): + super(TextListSubWidget, self).__init__(parent) + self.setObjectName("TextListSubWidget") + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + self.setLayout(layout) + + self.input_fields = [] + self.add_row() + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + self.set_value(value) + + self.origin_value = self.item_value() + + def set_value(self, value, origin_value=False): + for input_field in self.input_fields: + self.remove_row(input_field) + + for item_text in value: + self.add_row(text=item_text) + + if origin_value: + self.origin_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.origin_value) + + def clear_value(self): + self.set_value([]) + + def _on_value_change(self): + self.value_changed.emit() + + def count(self): + return len(self.input_fields) + + def add_row(self, row=None, text=None): + # Create new item + item_widget = TextListItem(self) + + # Set/unset if new item is single item + current_count = self.count() + if current_count == 0: + item_widget.is_single = True + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = False + + item_widget.value_changed.connect(self._on_value_change) + + if row is None: + self.layout().addWidget(item_widget) + self.input_fields.append(item_widget) + else: + self.layout().insertWidget(row, item_widget) + self.input_fields.insert(row, item_widget) + + # Set text if entered text is not None + # else (when add button clicked) trigger `_on_value_change` + if text is not None: + item_widget.text_input.setText(text) + else: + self._on_value_change() + self.parent().updateGeometry() + + def remove_row(self, item_widget): + item_widget.value_changed.disconnect() + + self.layout().removeWidget(item_widget) + self.input_fields.remove(item_widget) + item_widget.setParent(None) + item_widget.deleteLater() + + current_count = self.count() + if current_count == 0: + self.add_row() + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = True + + self._on_value_change() + self.parent().updateGeometry() + + def item_value(self): + output = [] + for item in self.input_fields: + text = item.config_value() + if text: + output.append(text) + + return output + + def config_value(self): + return {self.key: self.item_value()} + + +class TextListWidget(QtWidgets.QWidget, PypeConfigurationWidget): + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + super(TextListWidget, self).__init__(parent) + self.setObjectName("TextListWidget") + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + + if not label_widget: + label = input_data["label"] + label_widget = QtWidgets.QLabel(label) + layout.addWidget(label_widget) + + self.label_widget = label_widget + # keys = list(parent_keys) + # keys.append(input_data["key"]) + # self.keys = keys + + self.value_widget = TextListSubWidget( + input_data, values, parent_keys, self + ) + self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + self.value_widget.value_changed.connect(self._on_value_change) + + # self.value_widget.se + self.key = input_data["key"] + layout.addWidget(self.value_widget) + self.setLayout(layout) + + self.origin_value = self.item_value() + + @property + def is_overidable(self): + return self._parent.is_overidable + + def _on_value_change(self, value=None): + self.is_modified = self.item_value() != self.origin_value + self.is_overriden = True + + self._update_style() + + def set_value(self, value, origin_value=False): + self.value_widget.set_value(value) + if origin_value: + self.origin_value = self.item_value() + self._on_value_change() + + def reset_value(self): + self.set_value(self.origin_value) + + def clear_value(self): + self.set_value([]) + + def _update_style(self): + if self.is_overidable and self.is_overriden: + if self.is_modified: + state = "overriden-modified" + else: + state = "overriden" + elif self.is_modified: + state = "modified" + else: + state = "original" + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + def item_value(self): + return self.value_widget.config_value() + + def config_value(self): + return {self.key: self.item_value()} + + +class ExpandingWidget(QtWidgets.QWidget): + def __init__(self, label, parent): + super(ExpandingWidget, self).__init__(parent) + self.setObjectName("ExpandingWidget") + + 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("ExpandLabel") + + layout = QtWidgets.QHBoxLayout(top_part) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + layout.addWidget(button_toggle) + layout.addWidget(label_widget) + top_part.setLayout(layout) + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + self.top_part = top_part + self.button_toggle = button_toggle + self.label_widget = label_widget + + self.top_part.clicked.connect(self._top_part_clicked) + self.button_toggle.clicked.connect(self.toggle_content) + + def set_content_widget(self, content_widget): + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(9, 9, 9, 9) + + content_widget.setVisible(False) + + main_layout.addWidget(self.top_part) + main_layout.addWidget(content_widget) + self.setLayout(main_layout) + + self.content_widget = content_widget + + def _top_part_clicked(self): + self.toggle_content(not self.button_toggle.isChecked()) + + def toggle_content(self, *args): + if len(args) > 0: + checked = args[0] + else: + checked = 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 resizeEvent(self, event): + super(ExpandingWidget, self).resizeEvent(event) + self.content_widget.updateGeometry() + + +class DictExpandWidget(QtWidgets.QWidget, PypeConfigurationWidget): + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + if values is AS_WIDGET: + raise TypeError("Can't use \"{}\" as widget item.".format( + self.__class__.__name__ + )) + self._parent = parent + + super(DictExpandWidget, self).__init__(parent) + self.setObjectName("DictExpandWidget") + 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 = input_data["label"] + button_toggle_text = QtWidgets.QLabel(label, parent=top_part) + button_toggle_text.setObjectName("ExpandLabel") + + layout = QtWidgets.QHBoxLayout(top_part) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + layout.addWidget(button_toggle) + layout.addWidget(button_toggle_text) + top_part.setLayout(layout) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(9, 9, 9, 9) + + content_widget = QtWidgets.QWidget(self) + content_widget.setVisible(False) + + content_layout = QtWidgets.QVBoxLayout(content_widget) + content_layout.setContentsMargins(3, 3, 3, 3) + + main_layout.addWidget(top_part) + main_layout.addWidget(content_widget) + self.setLayout(main_layout) + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + self.top_part = top_part + self.button_toggle = button_toggle + self.button_toggle_text = button_toggle_text + + self.content_widget = content_widget + self.content_layout = content_layout + + self.top_part.clicked.connect(self._top_part_clicked) + self.button_toggle.clicked.connect(self.toggle_content) + + self._is_category = False + self._is_overriden = False + self.input_fields = [] + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + + def _top_part_clicked(self): + self.toggle_content(not self.button_toggle.isChecked()) + + def toggle_content(self, *args): + if len(args) > 0: + checked = args[0] + else: + checked = 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 resizeEvent(self, event): + super(DictExpandWidget, self).resizeEvent(event) + self.content_widget.updateGeometry() + + @property + def is_category(self): + return self._is_category + + @property + def is_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + _is_modified = False + for input_field in self.input_fields: + if input_field.is_modified: + _is_modified = True + break + return _is_modified + + 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.key: self.item_value()} + + @property + def is_overidable(self): + return self._parent.is_overidable + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + klass = TypeToKlass.types.get(item_type) + + item = klass( + child_configuration, values, self.keys, self + ) + self.content_layout.addWidget(item) + + self.input_fields.append(item) + return item + + +class DictInvisible(QtWidgets.QWidget, PypeConfigurationWidget): + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + self._parent = parent + + super(DictInvisible, self).__init__(parent) + self.setObjectName("DictInvisible") + + self.setAttribute(QtCore.Qt.WA_StyledBackground) + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(5) + + self._is_category = False + self._is_overriden = False + self.input_fields = [] + + if "key" not in input_data: + print(json.dumps(input_data, indent=4)) + + self.key = input_data["key"] + self.keys = list(parent_keys) + self.keys.append(self.key) + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + + @property + def is_overidable(self): + return self._parent.is_overidable + + @property + def is_category(self): + return self._is_category + + @property + def is_overriden(self): + return self._is_overriden + + @property + def is_modified(self): + _is_modified = False + for input_field in self.input_fields: + if input_field.is_modified: + _is_modified = True + break + return _is_modified + + 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.key: self.item_value()} + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + if item_type == "schema": + for _schema in child_configuration["children"]: + children = config.gui_schema(_schema) + self.add_children_gui(children, values) + return + + klass = TypeToKlass.types.get(item_type) + item = klass( + child_configuration, values, self.keys, self + ) + self.layout().addWidget(item) + + self.input_fields.append(item) + return item + + +class DictFormWidget(QtWidgets.QWidget): + def __init__( + self, input_data, values, parent_keys, parent, label_widget=None + ): + super(DictFormWidget, self).__init__(parent) + + self.input_fields = {} + self.content_layout = QtWidgets.QFormLayout(self) + + self.keys = list(parent_keys) + + for child_data in input_data.get("children", []): + self.add_children_gui(child_data, values) + + def item_value(self): + output = {} + for input_field in self.input_fields.values(): + # 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 + + @property + def is_overidable(self): + return self._parent.is_overidable + + def config_value(self): + return self.item_value() + + def add_children_gui(self, child_configuration, values): + item_type = child_configuration["type"] + key = child_configuration["key"] + # Pop label to not be set in child + label = child_configuration["label"] + + klass = TypeToKlass.types.get(item_type) + + label_widget = QtWidgets.QLabel(label) + item = klass( + child_configuration, values, self.keys, self, label_widget + ) + self.content_layout.addRow(label_widget, item) + self.input_fields[key] = item + return item + + +class TextListItem(QtWidgets.QWidget, PypeConfigurationWidget): + _btn_size = 20 + value_changed = QtCore.Signal() + + def __init__(self, parent): + super(TextListItem, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + + self.text_input = QtWidgets.QLineEdit() + self.add_btn = QtWidgets.QPushButton("+") + self.remove_btn = QtWidgets.QPushButton("-") + + self.add_btn.setProperty("btn-type", "text-list") + self.remove_btn.setProperty("btn-type", "text-list") + + layout.addWidget(self.text_input, 1) + layout.addWidget(self.add_btn, 0) + layout.addWidget(self.remove_btn, 0) + + 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.text_input.textChanged.connect(self._on_value_change) + + self.is_single = False + + def _on_value_change(self): + self.value_changed.emit() + + def row(self): + return self.parent().input_fields.index(self) + + def on_add_clicked(self): + self.parent().add_row(row=self.row() + 1) + + def on_remove_clicked(self): + if self.is_single: + self.text_input.setText("") + else: + self.parent().remove_row(self) + + def config_value(self): + return self.text_input.text() + + +class ModifiableDictItem(QtWidgets.QWidget, PypeConfigurationWidget): + _btn_size = 20 + value_changed = QtCore.Signal() + + def __init__(self, object_type, parent): + self._parent = parent + + super(ModifiableDictItem, self).__init__(parent) + + layout = QtWidgets.QHBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(3) + + ItemKlass = TypeToKlass.types[object_type] + + self.key_input = QtWidgets.QLineEdit() + self.key_input.setObjectName("DictKey") + + self.value_input = ItemKlass( + {}, + AS_WIDGET, + [], + self, + None + ) + self.add_btn = QtWidgets.QPushButton("+") + self.remove_btn = QtWidgets.QPushButton("-") + + self.add_btn.setProperty("btn-type", "text-list") + self.remove_btn.setProperty("btn-type", "text-list") + + layout.addWidget(self.key_input, 0) + layout.addWidget(self.value_input, 1) + layout.addWidget(self.add_btn, 0) + layout.addWidget(self.remove_btn, 0) + + 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 = self._key() + self.origin_value = self.value_input.item_value() + + self.is_single = False + + def _key(self): + return self.key_input.text() + + def _on_value_change(self): + self._update_style() + self.value_changed.emit() + + @property + def is_overidable(self): + return self._parent.is_overidable + + def _update_style(self): + is_modified = self._key() != self.origin_key + # if self._is_overidable and self.is_overriden: + # if is_modified: + # state = "overriden-modified" + # else: + # state = "overriden" + if is_modified: + state = "modified" + else: + state = "original" + + 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 on_add_clicked(self): + self.parent().add_row(row=self.row() + 1) + + def on_remove_clicked(self): + if self.is_single: + self.value_input.clear_value() + self.key_input.setText("") + else: + self.parent().remove_row(self) + + def config_value(self): + key = self.key_input.text() + value = self.value_input.item_value() + if not key: + return {} + return {key: value} + + +class ModifiableDictSubWidget(QtWidgets.QWidget, PypeConfigurationWidget): + value_changed = QtCore.Signal() + + def __init__(self, input_data, values, parent_keys, parent): + self._parent = parent + + super(ModifiableDictSubWidget, self).__init__(parent) + self.setObjectName("ModifiableDictSubWidget") + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(5, 5, 5, 5) + layout.setSpacing(5) + self.setLayout(layout) + + self.input_fields = [] + self.object_type = input_data["object_type"] + + self.key = input_data["key"] + keys = list(parent_keys) + keys.append(self.key) + self.keys = keys + + value = self.value_from_values(values) + if value is not NOT_SET: + for item_key, item_value in value.items(): + self.add_row(key=item_key, value=item_value) + + if self.count() == 0: + self.add_row() + + self.origin_value = self.config_value() + + @property + def is_overidable(self): + return self._parent.is_overidable + + def _on_value_change(self): + self.value_changed.emit() + + def count(self): + return len(self.input_fields) + + def add_row(self, row=None, key=None, value=None): + # Create new item + item_widget = ModifiableDictItem(self.object_type, self) + + # Set/unset if new item is single item + current_count = self.count() + if current_count == 0: + item_widget.is_single = True + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = False + + item_widget.value_changed.connect(self._on_value_change) + + if row is None: + self.layout().addWidget(item_widget) + self.input_fields.append(item_widget) + else: + self.layout().insertWidget(row, item_widget) + self.input_fields.insert(row, item_widget) + + # 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: + item_widget.origin_key = key + item_widget.key_input.setText(key) + item_widget.value_input.set_value(value, origin_value=True) + else: + self._on_value_change() + self.parent().updateGeometry() + + def remove_row(self, item_widget): + item_widget.value_changed.disconnect() + + self.layout().removeWidget(item_widget) + self.input_fields.remove(item_widget) + item_widget.setParent(None) + item_widget.deleteLater() + + current_count = self.count() + if current_count == 0: + self.add_row() + elif current_count == 1: + for _input_field in self.input_fields: + _input_field.is_single = True + + self._on_value_change() + self.parent().updateGeometry() + + def config_value(self): + output = {} + for item in self.input_fields: + item_value = item.config_value() + if item_value: + output.update(item_value) + return output + + +class ModifiableDict(ExpandingWidget, PypeConfigurationWidget): + def __init__( + self, input_data, values, parent_keys, parent, + label_widget=None + ): + self._parent = parent + + super(ModifiableDict, self).__init__(input_data["label"], parent) + self.setObjectName("ModifiableDict") + + self.value_widget = ModifiableDictSubWidget( + input_data, values, parent_keys, self + ) + self.value_widget.setAttribute(QtCore.Qt.WA_StyledBackground) + self.value_widget.value_changed.connect(self._on_value_change) + + self.set_content_widget(self.value_widget) + + self.key = input_data["key"] + + self.origin_value = self.item_value() + + def _on_value_change(self, value=None): + self.is_modified = self.item_value() != self.origin_value + self.is_overriden = True + + self._update_style() + + @property + def is_overidable(self): + return self._parent.is_overidable + + def _update_style(self): + if self.is_overidable and self.is_overriden: + if self.is_modified: + state = "overriden-modified" + else: + state = "overriden" + elif self.is_modified: + state = "modified" + else: + state = "original" + + self.label_widget.setProperty("state", state) + self.label_widget.style().polish(self.label_widget) + + def item_value(self): + return self.value_widget.config_value() + + def config_value(self): + return {self.key: self.item_value()} + + +TypeToKlass.types["boolean"] = BooleanWidget +TypeToKlass.types["text-singleline"] = TextSingleLineWidget +TypeToKlass.types["text-multiline"] = TextMultiLineWidget +TypeToKlass.types["int"] = IntegerWidget +TypeToKlass.types["float"] = FloatWidget +TypeToKlass.types["dict-expanding"] = DictExpandWidget +TypeToKlass.types["dict-form"] = DictFormWidget +TypeToKlass.types["dict-invisible"] = DictInvisible +TypeToKlass.types["dict-modifiable"] = ModifiableDict +TypeToKlass.types["list-text"] = TextListWidget diff --git a/pype/tools/config_setting/widgets/lib.py b/pype/tools/config_setting/widgets/lib.py new file mode 100644 index 0000000000..ac0a353d53 --- /dev/null +++ b/pype/tools/config_setting/widgets/lib.py @@ -0,0 +1,44 @@ +import uuid + + +class CustomNone: + """Created object can be used as custom None (not equal to None). + + WARNING: Multiple created objects are not equal either. + Exmple: + >>> a = CustomNone() + >>> a == None + False + >>> b = CustomNone() + >>> a == b + False + >>> a == a + True + """ + + def __init__(self): + """Create uuid as identifier for custom None.""" + self.identifier = str(uuid.uuid4()) + + def __bool__(self): + """Return False (like default None).""" + return False + + def __eq__(self, other): + """Equality is compared by identifier value.""" + if type(other) == type(self): + if other.identifier == self.identifier: + return True + return False + + def __str__(self): + """Return value of identifier when converted to string.""" + return "".format(str(self.identifier)) + + def __repr__(self): + """Representation of custom None.""" + return "".format(str(self.identifier)) + + +NOT_SET = CustomNone() +AS_WIDGET = CustomNone() diff --git a/pype/tools/config_setting/widgets/main.py b/pype/tools/config_setting/widgets/main.py new file mode 100644 index 0000000000..af23e68f77 --- /dev/null +++ b/pype/tools/config_setting/widgets/main.py @@ -0,0 +1,26 @@ +from Qt import QtWidgets +from .base import StudioWidget, ProjectWidget + + +class MainWidget(QtWidgets.QWidget): + widget_width = 1000 + widget_height = 600 + + def __init__(self, parent=None): + super(MainWidget, self).__init__(parent) + + self.resize(self.widget_width, self.widget_height) + + header_tab_widget = QtWidgets.QTabWidget(parent=self) + + studio_widget = StudioWidget() + project_widget = ProjectWidget() + header_tab_widget.addTab(studio_widget, "Studio") + header_tab_widget.addTab(project_widget, "Project") + + layout = QtWidgets.QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.setSpacing(0) + layout.addWidget(header_tab_widget) + + self.setLayout(layout) diff --git a/pype/tools/config_setting/widgets/tests.py b/pype/tools/config_setting/widgets/tests.py new file mode 100644 index 0000000000..53b67de3a1 --- /dev/null +++ b/pype/tools/config_setting/widgets/tests.py @@ -0,0 +1,127 @@ +from Qt import QtWidgets, QtCore + + +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