├── tests ├── fixtures │ ├── config │ │ ├── core │ │ │ ├── schema │ │ │ │ └── project │ │ │ │ │ └── assets │ │ │ │ │ ├── asset_type │ │ │ │ │ ├── asset │ │ │ │ │ │ ├── step │ │ │ │ │ │ │ ├── out │ │ │ │ │ │ │ │ └── .placeholder │ │ │ │ │ │ │ ├── images │ │ │ │ │ │ │ │ └── .placeholder │ │ │ │ │ │ │ ├── publish │ │ │ │ │ │ │ │ └── .placeholder │ │ │ │ │ │ │ ├── review │ │ │ │ │ │ │ │ └── .placeholder │ │ │ │ │ │ │ └── work │ │ │ │ │ │ │ │ ├── task │ │ │ │ │ │ │ │ └── placeholder │ │ │ │ │ │ │ │ ├── user │ │ │ │ │ │ │ │ └── snapshots │ │ │ │ │ │ │ │ │ └── .placeholder │ │ │ │ │ │ │ │ ├── user.yml │ │ │ │ │ │ │ │ └── task.yml │ │ │ │ │ │ └── step.yml │ │ │ │ │ └── asset.yml │ │ │ │ │ └── asset_type.yml │ │ │ ├── core_api.yml │ │ │ ├── hooks │ │ │ │ └── pick_environment.py │ │ │ └── templates.yml │ │ └── env │ │ │ ├── project.yml │ │ │ └── task.yml │ ├── files │ │ └── images │ │ │ └── sven.png │ └── configWF2ui │ │ ├── core │ │ ├── schema │ │ │ ├── project │ │ │ │ └── assets │ │ │ │ │ ├── asset_type │ │ │ │ │ ├── asset │ │ │ │ │ │ ├── step │ │ │ │ │ │ │ ├── work │ │ │ │ │ │ │ │ └── images │ │ │ │ │ │ │ │ │ └── placeholder │ │ │ │ │ │ │ └── publish │ │ │ │ │ │ │ │ └── images │ │ │ │ │ │ │ │ └── placeholder │ │ │ │ │ │ └── step.yml │ │ │ │ │ └── asset.yml │ │ │ │ │ └── asset_type.yml │ │ │ └── project.yml │ │ ├── core_api.yml │ │ ├── hooks │ │ │ └── pick_environment.py │ │ ├── templates.yml │ │ └── roots.yml │ │ └── env │ │ ├── shot.yml │ │ ├── asset.yml │ │ ├── sequence.yml │ │ ├── project.yml │ │ ├── asset_step.yml │ │ └── shot_step.yml ├── test_file_open_gui.py ├── test_file_save_gui.py ├── conftest.py └── test_change_context_gui.py ├── icon_256.png ├── resources ├── padlock.png ├── search.png ├── users_all.png ├── clear_search.png ├── publish_icon.png ├── save_as_icon.png ├── save_expand.png ├── thumb_empty.png ├── users_none.png ├── users_other.png ├── badge_default.png ├── folder_512x400.png ├── save_collapse.png ├── users_current.png ├── grid_view_checked.png ├── save_expand_hover.png ├── users_all_hover.png ├── users_none_hover.png ├── users_other_hover.png ├── clear_search_hover.png ├── file_open_menu_icon.png ├── file_save_menu_icon.png ├── grid_view_unchecked.png ├── reference │ ├── icons.graffle │ └── app_breakdown.graffle ├── save_collapse_hover.png ├── save_expand_pressed.png ├── tree_arrow_expanded.png ├── users_current_hover.png ├── details_view_checked.png ├── details_view_unchecked.png ├── save_collapse_pressed.png ├── tree_arrow_collapsed.png ├── grid_view_checked_hover.png ├── details_view_checked_hover.png ├── grid_view_unchecked_hover.png ├── details_view_unchecked_hover.png ├── crash_dbg_form.ui ├── resources.qrc ├── my_tasks_form.ui ├── file_open_form.ui └── entity_tree_form.ui ├── codecov.yaml ├── .gitignore ├── python ├── tk_multi_workfiles │ ├── actions │ │ ├── __init__.py │ │ ├── context_change_action.py │ │ ├── show_in_shotgun_action.py │ │ ├── action.py │ │ ├── save_as_file_action.py │ │ ├── new_task_action.py │ │ ├── open_publish_actions.py │ │ ├── open_workfile_actions.py │ │ └── custom_file_action.py │ ├── my_tasks │ │ ├── __init__.py │ │ └── my_tasks_form.py │ ├── entity_tree │ │ ├── __init__.py │ │ └── entity_tree_proxy_model.py │ ├── file_list │ │ ├── __init__.py │ │ ├── file_details_view.py │ │ └── user_filter_button.py │ ├── ui │ │ ├── __init__.py │ │ ├── entity_widget.py │ │ ├── crash_dbg_form.py │ │ ├── my_tasks_form.py │ │ ├── entity_tree_form.py │ │ └── file_open_form.py │ ├── entity_models │ │ └── __init__.py │ ├── __init__.py │ ├── sg_published_files_model.py │ ├── errors.py │ ├── wrapper_dialog.py │ ├── framework_qtwidgets.py │ ├── context_change_form.py │ ├── file_filters.py │ ├── entity_proxy_model.py │ └── work_files.py └── __init__.py ├── .coveragerc ├── style.qss ├── hooks ├── copy_file.py ├── filter_publishes.py ├── scene_operation_tk-mari.py ├── filter_work_files.py ├── scene_operation_tk-houdini.py ├── scene_operation_tk-3dsmaxplus.py ├── scene_operation_tk-shell.py ├── create_new_task.py ├── get_badge.py ├── scene_operation_tk-photoshopcc.py ├── scene_operation_tk-photoshop.py ├── scene_operation_tk-motionbuilder.py └── scene_operation_tk-maya.py ├── azure-pipelines.yml ├── SECURITY.md ├── .pre-commit-config.yaml └── README.md /tests/fixtures/config/core/schema/project/assets/asset_type/asset/step/out/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/config/core/schema/project/assets/asset_type/asset/step/images/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/config/core/schema/project/assets/asset_type/asset/step/publish/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/config/core/schema/project/assets/asset_type/asset/step/review/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/fixtures/config/core/schema/project/assets/asset_type/asset/step/work/task/placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/icon_256.png -------------------------------------------------------------------------------- /tests/fixtures/config/core/schema/project/assets/asset_type/asset/step/work/user/snapshots/.placeholder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/padlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/padlock.png -------------------------------------------------------------------------------- /resources/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/search.png -------------------------------------------------------------------------------- /resources/users_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/users_all.png -------------------------------------------------------------------------------- /resources/clear_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/clear_search.png -------------------------------------------------------------------------------- /resources/publish_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/publish_icon.png -------------------------------------------------------------------------------- /resources/save_as_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/save_as_icon.png -------------------------------------------------------------------------------- /resources/save_expand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/save_expand.png -------------------------------------------------------------------------------- /resources/thumb_empty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/thumb_empty.png -------------------------------------------------------------------------------- /resources/users_none.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/users_none.png -------------------------------------------------------------------------------- /resources/users_other.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/users_other.png -------------------------------------------------------------------------------- /resources/badge_default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/badge_default.png -------------------------------------------------------------------------------- /resources/folder_512x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/folder_512x400.png -------------------------------------------------------------------------------- /resources/save_collapse.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/save_collapse.png -------------------------------------------------------------------------------- /resources/users_current.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/users_current.png -------------------------------------------------------------------------------- /resources/grid_view_checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/grid_view_checked.png -------------------------------------------------------------------------------- /resources/save_expand_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/save_expand_hover.png -------------------------------------------------------------------------------- /resources/users_all_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/users_all_hover.png -------------------------------------------------------------------------------- /resources/users_none_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/users_none_hover.png -------------------------------------------------------------------------------- /resources/users_other_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/users_other_hover.png -------------------------------------------------------------------------------- /resources/clear_search_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/clear_search_hover.png -------------------------------------------------------------------------------- /resources/file_open_menu_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/file_open_menu_icon.png -------------------------------------------------------------------------------- /resources/file_save_menu_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/file_save_menu_icon.png -------------------------------------------------------------------------------- /resources/grid_view_unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/grid_view_unchecked.png -------------------------------------------------------------------------------- /resources/reference/icons.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/reference/icons.graffle -------------------------------------------------------------------------------- /resources/save_collapse_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/save_collapse_hover.png -------------------------------------------------------------------------------- /resources/save_expand_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/save_expand_pressed.png -------------------------------------------------------------------------------- /resources/tree_arrow_expanded.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/tree_arrow_expanded.png -------------------------------------------------------------------------------- /resources/users_current_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/users_current_hover.png -------------------------------------------------------------------------------- /resources/details_view_checked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/details_view_checked.png -------------------------------------------------------------------------------- /resources/details_view_unchecked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/details_view_unchecked.png -------------------------------------------------------------------------------- /resources/save_collapse_pressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/save_collapse_pressed.png -------------------------------------------------------------------------------- /resources/tree_arrow_collapsed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/tree_arrow_collapsed.png -------------------------------------------------------------------------------- /tests/fixtures/files/images/sven.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/tests/fixtures/files/images/sven.png -------------------------------------------------------------------------------- /resources/grid_view_checked_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/grid_view_checked_hover.png -------------------------------------------------------------------------------- /resources/details_view_checked_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/details_view_checked_hover.png -------------------------------------------------------------------------------- /resources/grid_view_unchecked_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/grid_view_unchecked_hover.png -------------------------------------------------------------------------------- /resources/reference/app_breakdown.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/reference/app_breakdown.graffle -------------------------------------------------------------------------------- /resources/details_view_unchecked_hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shotgunsoftware/tk-multi-workfiles2/HEAD/resources/details_view_unchecked_hover.png -------------------------------------------------------------------------------- /codecov.yaml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | # basic 6 | threshold: 0.5% 7 | base: auto 8 | only_pulls: true 9 | -------------------------------------------------------------------------------- /tests/fixtures/configWF2ui/core/schema/project/assets/asset_type/asset/step/work/images/placeholder: -------------------------------------------------------------------------------- 1 | # This file is a placeholder to ensure that the parent folder is preserved and not deleted by git. 2 | # Any file named 'placeholder' will not be copied across when folders are created. 3 | # Note: You can which files should be ignored when folders are created in the ignore_files file, 4 | # located in the schema folder. 5 | -------------------------------------------------------------------------------- /tests/fixtures/configWF2ui/core/schema/project/assets/asset_type/asset/step/publish/images/placeholder: -------------------------------------------------------------------------------- 1 | # This file is a placeholder to ensure that the parent folder is preserved and not deleted by git. 2 | # Any file named 'placeholder' will not be copied across when folders are created. 3 | # Note: You can which files should be ignored when folders are created in the ignore_files file, 4 | # located in the schema folder. 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.py[cod] 2 | 3 | # C extensions 4 | *.so 5 | 6 | # Packages 7 | *.egg 8 | *.egg-info 9 | dist 10 | build 11 | eggs 12 | parts 13 | bin 14 | var 15 | sdist 16 | develop-eggs 17 | .installed.cfg 18 | lib 19 | lib64 20 | 21 | # Installer logs 22 | pip-log.txt 23 | 24 | # Unit test / coverage reports 25 | .coverage 26 | htmlcov/ 27 | .tox 28 | nosetests.xml 29 | 30 | # Translations 31 | *.mo 32 | 33 | # Mr Developer 34 | .mr.developer.cfg 35 | .project 36 | .pydevproject 37 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/actions/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/my_tasks/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/entity_tree/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/file_list/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/ui/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | -------------------------------------------------------------------------------- /python/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | from . import tk_multi_workfiles 12 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/entity_models/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2017 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | from .deferred_model import ShotgunDeferredEntityModel 12 | from .extended_model import ShotgunExtendedEntityModel 13 | -------------------------------------------------------------------------------- /tests/fixtures/config/core/schema/project/assets/asset_type/asset/step/work/user.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # the type of dynamic content 12 | type: "user_workspace" 13 | 14 | name: "login" 15 | -------------------------------------------------------------------------------- /tests/fixtures/configWF2ui/core/schema/project.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # the type of dynamic content 12 | type: "project" 13 | 14 | # name of project root as defined in roots.yml 15 | root_name: "Toolkit UI Automation" 16 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | from .work_files import WorkFiles 12 | 13 | # Leaving this in to make it easier to test the dialogs through scripting. 14 | from .file_open_form import FileOpenForm 15 | -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | # 11 | # coverage configuration - used by https://coveralls.io/ integration 12 | # 13 | # 14 | [run] 15 | source=. 16 | omit=tests/* 17 | 18 | [report] 19 | exclude_lines = 20 | raise NotImplementedError 21 | -------------------------------------------------------------------------------- /tests/fixtures/config/core/schema/project/assets/asset_type/asset/step.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # the type of dynamic content 12 | type: "shotgun_step" 13 | 14 | # the shotgun field to use for the folder name 15 | name: "short_name" 16 | -------------------------------------------------------------------------------- /tests/fixtures/configWF2ui/core/schema/project/assets/asset_type/asset/step.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # the type of dynamic content 12 | type: "shotgun_step" 13 | 14 | # the shotgun field to use for the folder name 15 | name: "short_name" 16 | -------------------------------------------------------------------------------- /tests/fixtures/config/core/core_api.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # this configuration file defines which version of the toolkit 12 | # core API that should be used at runtime. 13 | 14 | location: 15 | type: path 16 | path: $SHOTGUN_REPOS_ROOT/tk-core 17 | -------------------------------------------------------------------------------- /tests/fixtures/configWF2ui/core/core_api.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # this configuration file defines which version of the toolkit 12 | # core API that should be used at runtime. 13 | 14 | location: 15 | type: path 16 | path: $SHOTGUN_REPOS_ROOT/tk-core 17 | -------------------------------------------------------------------------------- /tests/fixtures/config/core/schema/project/assets/asset_type/asset/step/work/task.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # the type of dynamic content 12 | type: "shotgun_task" 13 | 14 | # the shotgun field to use for the folder name. This field needs to come from a task entity. 15 | name: "content" 16 | -------------------------------------------------------------------------------- /tests/fixtures/config/core/hooks/pick_environment.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | 12 | from tank import Hook 13 | 14 | 15 | class PickEnvironment(Hook): 16 | def execute(self, context): 17 | if context.task is None: 18 | return "project" 19 | else: 20 | return "task" 21 | -------------------------------------------------------------------------------- /tests/fixtures/config/core/schema/project/assets/asset_type.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # the type of dynamic content 12 | type: "shotgun_list_field" 13 | 14 | # the shotgun entity type to connect to 15 | entity_type: "Asset" 16 | 17 | # the shotgun field to use for the folder name 18 | field_name: "sg_asset_type" 19 | -------------------------------------------------------------------------------- /tests/fixtures/configWF2ui/core/schema/project/assets/asset_type.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # the type of dynamic content 12 | type: "shotgun_list_field" 13 | 14 | # the shotgun entity type to connect to 15 | entity_type: "Asset" 16 | 17 | # the shotgun field to use for the folder name 18 | field_name: "sg_asset_type" 19 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/file_list/file_details_view.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ """ 12 | 13 | import sgtk 14 | from sgtk.platform.qt import QtCore, QtGui 15 | 16 | 17 | class FileDetailsView(QtGui.QTableView): 18 | """ """ 19 | 20 | def __init__(self, parent): 21 | """ 22 | Construction 23 | """ 24 | QtGui.QTableView.__init__(self, parent) 25 | -------------------------------------------------------------------------------- /resources/crash_dbg_form.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | CrashDbgForm 4 | 5 | 6 | 7 | 0 8 | 0 9 | 503 10 | 395 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/fixtures/configWF2ui/env/shot.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | engines: 12 | tk-testengine: 13 | location: {'type': 'dev', 'path': '$SHOTGUN_TEST_ENGINE'} 14 | debug_logging: false 15 | apps: 16 | tk-multi-run-this-app: 17 | location: {'type': 'path', 'path': '$SHOTGUN_TK_APP_LOCATION'} 18 | 19 | frameworks: 20 | tk-framework-shotgunutils_v5.x.x: 21 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-shotgunutils'} 22 | tk-framework-qtwidgets_v2.x.x: 23 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-qtwidgets'} 24 | -------------------------------------------------------------------------------- /tests/fixtures/configWF2ui/env/asset.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | engines: 12 | tk-testengine: 13 | location: {'type': 'dev', 'path': '$SHOTGUN_TEST_ENGINE'} 14 | debug_logging: false 15 | apps: 16 | tk-multi-run-this-app: 17 | location: {'type': 'path', 'path': '$SHOTGUN_TK_APP_LOCATION'} 18 | 19 | frameworks: 20 | tk-framework-shotgunutils_v5.x.x: 21 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-shotgunutils'} 22 | tk-framework-qtwidgets_v2.x.x: 23 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-qtwidgets'} 24 | -------------------------------------------------------------------------------- /tests/fixtures/configWF2ui/env/sequence.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | engines: 12 | tk-testengine: 13 | location: {'type': 'dev', 'path': '$SHOTGUN_TEST_ENGINE'} 14 | debug_logging: false 15 | apps: 16 | tk-multi-run-this-app: 17 | location: {'type': 'path', 'path': '$SHOTGUN_TK_APP_LOCATION'} 18 | 19 | frameworks: 20 | tk-framework-shotgunutils_v5.x.x: 21 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-shotgunutils'} 22 | tk-framework-qtwidgets_v2.x.x: 23 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-qtwidgets'} 24 | -------------------------------------------------------------------------------- /tests/fixtures/configWF2ui/env/project.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | engines: 12 | tk-testengine: 13 | location: {'type': 'dev', 'path': '$SHOTGUN_TEST_ENGINE'} 14 | debug_logging: false 15 | apps: 16 | tk-multi-run-this-app: 17 | location: {'type': 'path', 'path': '$SHOTGUN_TK_APP_LOCATION'} 18 | show_change_context: true 19 | 20 | frameworks: 21 | tk-framework-shotgunutils_v5.x.x: 22 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-shotgunutils'} 23 | tk-framework-qtwidgets_v2.x.x: 24 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-qtwidgets'} 25 | -------------------------------------------------------------------------------- /tests/fixtures/config/core/schema/project/assets/asset_type/asset.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # the type of dynamic content 12 | type: "shotgun_entity" 13 | 14 | # the shotgun field to use for the folder name 15 | name: "code" 16 | 17 | # the shotgun entity type to connect to 18 | entity_type: "Asset" 19 | 20 | # shotgun filters to apply when getting the list of items 21 | # this should be a list of dicts, each dict containing 22 | # three fields: path, relation and values 23 | # (this is std shotgun API syntax) 24 | # any values starting with $ are resolved into path objects 25 | filters: 26 | - { "path": "project", "relation": "is", "values": [ "$project" ] } 27 | - { "path": "sg_asset_type", "relation": "is", "values": [ "$asset_type"] } 28 | -------------------------------------------------------------------------------- /tests/fixtures/configWF2ui/core/schema/project/assets/asset_type/asset.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # the type of dynamic content 12 | type: "shotgun_entity" 13 | 14 | # the shotgun field to use for the folder name 15 | name: "code" 16 | 17 | # the shotgun entity type to connect to 18 | entity_type: "Asset" 19 | 20 | # shotgun filters to apply when getting the list of items 21 | # this should be a list of dicts, each dict containing 22 | # three fields: path, relation and values 23 | # (this is std shotgun API syntax) 24 | # any values starting with $ are resolved into path objects 25 | filters: 26 | - { "path": "project", "relation": "is", "values": [ "$project" ] } 27 | - { "path": "sg_asset_type", "relation": "is", "values": [ "$asset_type"] } 28 | -------------------------------------------------------------------------------- /style.qss: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2017 Shotgun Software Inc 3 | 4 | CONFIDENTIAL AND PROPRIETARY 5 | 6 | This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 7 | Source Code License included in this distribution package. See LICENSE. 8 | By accessing, using, copying or modifying this work you indicate your 9 | agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 10 | not expressly granted therein are reserved by Shotgun Software Inc. 11 | */ 12 | 13 | /* Use open sans font across the app if core supports it */ 14 | QWidget { 15 | font-family: Open Sans; 16 | font-style: Regular; 17 | } 18 | 19 | #step_filter_label { 20 | font-size: 9px; 21 | } 22 | 23 | #step_filter_list_widget QCheckBox { 24 | icon-size: 10px 10px; 25 | padding-left: 10px; 26 | } 27 | 28 | #step_filters_frame QPushButton { 29 | font-size: 9px; 30 | } 31 | 32 | QToolButton#thumbnail_mode, 33 | QToolButton#list_mode 34 | { 35 | border: none; 36 | outline: none; 37 | padding: 4px; 38 | } 39 | QToolButton#thumbnail_mode:checked, 40 | QToolButton#list_mode:checked, 41 | QToolButton#thumbnail_mode:hover, 42 | QToolButton#list_mode:hover 43 | { 44 | background-color: palette(light); 45 | border-radius: 4px; 46 | } 47 | -------------------------------------------------------------------------------- /tests/fixtures/config/env/project.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | engines: 12 | tk-testengine: 13 | location: {'type': 'dev', 'path': '$SHOTGUN_TEST_ENGINE'} 14 | debug_logging: false 15 | apps: 16 | tk-multi-workfiles2: 17 | location: {'type': 'dev', 'path': '$SHOTGUN_CURRENT_REPO_ROOT'} 18 | tk-multi-workfiles2-with-tasks: 19 | location: {'type': 'dev', 'path': '$SHOTGUN_CURRENT_REPO_ROOT'} 20 | 21 | frameworks: 22 | tk-framework-shotgunutils_v5.x.x: 23 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-shotgunutils'} 24 | tk-framework-qtwidgets_v2.x.x: 25 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-qtwidgets'} 26 | tk-framework-shotgunutils_v0.2.x: 27 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-widget'} 28 | -------------------------------------------------------------------------------- /hooks/copy_file.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | import os 12 | import shutil 13 | import sgtk 14 | 15 | HookClass = sgtk.get_hook_baseclass() 16 | 17 | 18 | class CopyFile(HookClass): 19 | """ 20 | Hook called when a file needs to be copied 21 | """ 22 | 23 | def execute(self, source_path, target_path, **kwargs): 24 | """ 25 | Main hook entry point 26 | 27 | :source_path: String 28 | Source file path to copy 29 | 30 | :target_path: String 31 | Target file path to copy to 32 | """ 33 | 34 | # create the folder if it doesn't exist 35 | dirname = os.path.dirname(target_path) 36 | if not os.path.isdir(dirname): 37 | old_umask = os.umask(0) 38 | os.makedirs(dirname, 0o777) 39 | os.umask(old_umask) 40 | 41 | shutil.copy(source_path, target_path) 42 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # Imports the shared Azure CI tools 12 | resources: 13 | repositories: 14 | - repository: templates 15 | type: github 16 | name: shotgunsoftware/tk-ci-tools 17 | ref: refs/heads/master 18 | endpoint: shotgunsoftware 19 | 20 | # We want builds to trigger for 3 reasons: 21 | # - The master branch sees new commits 22 | # - Each PR should get rebuilt when commits are added to it. 23 | # - When we tag something 24 | trigger: 25 | branches: 26 | include: 27 | - master 28 | tags: 29 | include: 30 | - v* 31 | pr: 32 | branches: 33 | include: 34 | - "*" 35 | 36 | # This pulls in a variable group from Azure. Variables can be encrypted or not. 37 | variables: 38 | - group: deploy-secrets 39 | 40 | # Launch into the build pipeline. 41 | jobs: 42 | - template: build-pipeline.yml@templates 43 | parameters: 44 | 45 | additional_repositories: 46 | - name: tk-framework-shotgunutils 47 | - name: tk-framework-qtwidgets 48 | -------------------------------------------------------------------------------- /tests/fixtures/config/env/task.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | engines: 12 | tk-testengine: 13 | location: {'type': 'dev', 'path': '$SHOTGUN_TEST_ENGINE'} 14 | debug_logging: false 15 | apps: 16 | tk-multi-workfiles2: 17 | template_publish: publish_path 18 | template_publish_area: publish_area 19 | template_work: sandbox_path 20 | template_work_area: work_area 21 | location: {'type': 'dev', 'path': '$SHOTGUN_CURRENT_REPO_ROOT'} 22 | tk-multi-workfiles2-with-tasks: 23 | template_publish: publish_path 24 | template_publish_area: publish_area 25 | template_work: task_path 26 | template_work_area: work_area 27 | location: {'type': 'dev', 'path': '$SHOTGUN_CURRENT_REPO_ROOT'} 28 | 29 | frameworks: 30 | tk-framework-shotgunutils_v5.x.x: 31 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-shotgunutils' } 32 | tk-framework-shotgunutils_v0.2.x: 33 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-widget' } 34 | -------------------------------------------------------------------------------- /hooks/filter_publishes.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | import sgtk 12 | 13 | HookClass = sgtk.get_hook_baseclass() 14 | 15 | 16 | class FilterPublishes(HookClass): 17 | """ 18 | Hook that can be used to filter the list of publishes returned from Shotgun for the current 19 | Work area 20 | """ 21 | 22 | def execute(self, publishes, **kwargs): 23 | """ 24 | Main hook entry point 25 | 26 | :param publishes: List of dictionaries 27 | A list of dictionaries for the current work area within the app. Each 28 | item in the list is a Dictionary of the form: 29 | 30 | { 31 | "sg_publish" : {Shotgun entity dictionary for a Published File entity} 32 | } 33 | 34 | 35 | :returns: The filtered list of dictionaries of the same form as the input 'publishes' 36 | list 37 | """ 38 | app = self.parent 39 | 40 | # the default implementation just returns the unfiltered list: 41 | return publishes 42 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Security 4 | 5 | At Autodesk, we know that the security of your data is critical to your studio’s 6 | operation. 7 | As the industry shifts to the cloud, Flow Production Tracking knows that security 8 | and service models are more important than ever. 9 | 10 | The confidentiality, integrity, and availability of your content is at the top 11 | of our priority list. 12 | Not only do we have a team of Flow Production Tracking engineers dedicated to 13 | platform security and performance, we are also backed by Autodesk’s security team, 14 | also invests heavily in the security for broad range of industries and customers. 15 | We constantly reassess, develop, and improve our risk management program because 16 | we know that the landscape of security is ever-changing. 17 | 18 | If you believe you have found a security vulnerability in any 19 | Flow Production Tracking-owned repository, please report it to us as described below. 20 | 21 | 22 | ## Reporting Security Issues 23 | 24 | **Please do not report security vulnerabilities through public GitHub issues.** 25 | 26 | Instead, please report them by sending a message to 27 | [Autodesk Trust Center](https://www.autodesk.com/trust/contact-us). 28 | 29 | Please include as much information as you can provide such as locations, 30 | configurations, reproduction steps, exploit code, impact, etc. 31 | 32 | 33 | ## Additional Information 34 | 35 | Please check out the [Flow Production Tracking Security White Paper](https://help.autodesk.com/view/SGSUB/ENU/?guid=SG_Administrator_ar_general_security_ar_security_white_paper_html). 36 | -------------------------------------------------------------------------------- /resources/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | save_collapse_pressed.png 4 | users_current_hover.png 5 | users_none_hover.png 6 | users_other_hover.png 7 | users_all_hover.png 8 | users_all.png 9 | users_other.png 10 | users_none.png 11 | users_current.png 12 | save_collapse_hover.png 13 | save_collapse.png 14 | save_expand_pressed.png 15 | save_expand_hover.png 16 | save_expand.png 17 | publish_icon.png 18 | folder_512x400.png 19 | tree_arrow_expanded.png 20 | tree_arrow_collapsed.png 21 | clear_search_hover.png 22 | clear_search.png 23 | details_view_checked_hover.png 24 | details_view_unchecked_hover.png 25 | grid_view_unchecked_hover.png 26 | grid_view_checked_hover.png 27 | details_view_unchecked.png 28 | grid_view_checked.png 29 | grid_view_unchecked.png 30 | details_view_checked.png 31 | search.png 32 | save_as_icon.png 33 | thumb_empty.png 34 | padlock.png 35 | badge_default.png 36 | 37 | 38 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # Styles the code properly 12 | # Exclude the UI files, as they are auto-generated. 13 | exclude: "ui\/.*py$" 14 | # List of super useful formatters. 15 | repos: 16 | - repo: https://github.com/pre-commit/pre-commit-hooks 17 | rev: v5.0.0 18 | hooks: 19 | # Ensures the code is syntaxically correct 20 | - id: check-ast 21 | language_version: python3 22 | # Ensures a file name will resolve on all platform 23 | - id: check-case-conflict 24 | # Checks files with the execute bit set have shebangs 25 | - id: check-executables-have-shebangs 26 | # Ensure there's no incomplete merges 27 | - id: check-merge-conflict 28 | # Adds an empty line if missing at the end of a file. 29 | - id: end-of-file-fixer 30 | # Makes sure yaml files are syntactically valid. 31 | - id: check-yaml 32 | # Makes sure requirements.txt is properly formatted 33 | - id: requirements-txt-fixer 34 | # Removes trailing whitespaces. 35 | - id: trailing-whitespace 36 | # Leave black at the bottom so all touchups are done before it is run. 37 | - repo: https://github.com/psf/black 38 | rev: 25.1.0 39 | hooks: 40 | - id: black 41 | language_version: python3 42 | -------------------------------------------------------------------------------- /tests/fixtures/configWF2ui/env/asset_step.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | engines: 12 | tk-testengine: 13 | location: {'type': 'dev', 'path': '$SHOTGUN_TEST_ENGINE'} 14 | debug_logging: false 15 | apps: 16 | tk-multi-run-this-app: 17 | template_publish: publish_path 18 | template_publish_area: publish_area 19 | template_work_area: work_area 20 | template_work: work_path 21 | entities: 22 | - caption: Assets 23 | entity_type: Asset 24 | hierarchy: [sg_asset_type, code] 25 | filters: 26 | sub_hierarchy: 27 | entity_type: Task 28 | filters: 29 | link_field: entity 30 | hierarchy: [step] 31 | - caption: Shots 32 | entity_type: Shot 33 | filters: 34 | hierarchy: [sg_sequence, code] 35 | sub_hierarchy: 36 | entity_type: Task 37 | filters: 38 | link_field: entity 39 | hierarchy: [step] 40 | location: {'type': 'path', 'path': '$SHOTGUN_TK_APP_LOCATION'} 41 | 42 | 43 | frameworks: 44 | tk-framework-shotgunutils_v5.x.x: 45 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-shotgunutils' } 46 | tk-framework-qtwidgets_v2.x.x: 47 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-qtwidgets' } 48 | -------------------------------------------------------------------------------- /tests/fixtures/configWF2ui/env/shot_step.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | engines: 12 | tk-testengine: 13 | location: {'type': 'dev', 'path': '$SHOTGUN_TEST_ENGINE'} 14 | debug_logging: false 15 | apps: 16 | tk-multi-run-this-app: 17 | template_publish: publish_path 18 | template_publish_area: publish_area 19 | template_work_area: work_area 20 | template_work: work_path 21 | entities: 22 | - caption: Assets 23 | entity_type: Asset 24 | hierarchy: [sg_asset_type, code] 25 | filters: 26 | sub_hierarchy: 27 | entity_type: Task 28 | filters: 29 | link_field: entity 30 | hierarchy: [step] 31 | - caption: Shots 32 | entity_type: Shot 33 | filters: 34 | hierarchy: [sg_sequence, code] 35 | sub_hierarchy: 36 | entity_type: Task 37 | filters: 38 | link_field: entity 39 | hierarchy: [step] 40 | location: {'type': 'path', 'path': '$SHOTGUN_TK_APP_LOCATION'} 41 | 42 | 43 | frameworks: 44 | tk-framework-shotgunutils_v5.x.x: 45 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-shotgunutils' } 46 | tk-framework-qtwidgets_v2.x.x: 47 | location: {type: path, path: '$SHOTGUN_REPOS_ROOT/tk-framework-qtwidgets' } 48 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/ui/entity_widget.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # Form implementation generated from reading ui file 'entity_widget.ui' 4 | # 5 | # by: pyside-uic 0.2.15 running on PySide 1.2.4 6 | # 7 | # WARNING! All changes made in this file will be lost! 8 | 9 | from sgtk.platform.qt import QtCore, QtGui 10 | 11 | class Ui_entity_frame(object): 12 | def setupUi(self, entity_frame): 13 | entity_frame.setObjectName("entity_frame") 14 | entity_frame.resize(184, 39) 15 | entity_frame.setFrameShape(QtGui.QFrame.StyledPanel) 16 | entity_frame.setFrameShadow(QtGui.QFrame.Raised) 17 | self.horizontalLayout = QtGui.QHBoxLayout(entity_frame) 18 | self.horizontalLayout.setContentsMargins(0, 0, 0, 0) 19 | self.horizontalLayout.setObjectName("horizontalLayout") 20 | self.icon_label = QtGui.QLabel(entity_frame) 21 | self.icon_label.setMaximumSize(QtCore.QSize(20, 20)) 22 | self.icon_label.setText("") 23 | self.icon_label.setObjectName("icon_label") 24 | self.horizontalLayout.addWidget(self.icon_label) 25 | self.title_label = QtGui.QLabel(entity_frame) 26 | self.title_label.setText("") 27 | self.title_label.setObjectName("title_label") 28 | self.horizontalLayout.addWidget(self.title_label) 29 | self.detail_label = QtGui.QLabel(entity_frame) 30 | self.detail_label.setText("") 31 | self.detail_label.setObjectName("detail_label") 32 | self.horizontalLayout.addWidget(self.detail_label) 33 | 34 | self.retranslateUi(entity_frame) 35 | QtCore.QMetaObject.connectSlotsByName(entity_frame) 36 | 37 | def retranslateUi(self, entity_frame): 38 | entity_frame.setWindowTitle(QtGui.QApplication.translate("entity_frame", "Frame", None, QtGui.QApplication.UnicodeUTF8)) 39 | 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![VFX Platform](https://img.shields.io/badge/vfxplatform-2025%20%7C%202024%20%7C%202023%20%7C%202022-blue.svg)](http://www.vfxplatform.com/) 2 | [![Python](https://img.shields.io/badge/python-3.11%20%7C%203.10%20%7C%203.9-blue.svg)](https://www.python.org/) 3 | [![Build Status](https://dev.azure.com/shotgun-ecosystem/Toolkit/_apis/build/status/Apps/tk-multi-workfiles2?repoName=shotgunsoftware%2Ftk-multi-workfiles2&branchName=refs%2Fpull%2F91%2Fmerge)](https://dev.azure.com/shotgun-ecosystem/Toolkit/_build/latest?definitionId=50&repoName=shotgunsoftware%2Ftk-multi-workfiles2&branchName=refs%2Fpull%2F91%2Fmerge) 4 | [![codecov](https://codecov.io/gh/shotgunsoftware/tk-multi-workfiles2/branch/master/graph/badge.svg)](https://codecov.io/gh/shotgunsoftware/tk-multi-workfiles2) 5 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 6 | [![Linting](https://img.shields.io/badge/PEP8%20by-Hound%20CI-a873d1.svg)](https://houndci.com) 7 | 8 | ## Documentation 9 | This repository is a part of the Flow Production Tracking Toolkit. 10 | 11 | - For more information about this app and for release notes, *see the wiki section*. 12 | - For general information and documentation, click here: https://help.autodesk.com/view/SGDEV/ENU/?contextId=SA_INTEGRATIONS_USER_GUIDE 13 | - For information about Flow Production Tracking in general, click here: https://help.autodesk.com/view/SGSUB/ENU/ 14 | 15 | ## Using this app in your Setup 16 | All the apps that are part of our standard app suite are pushed to our App Store. 17 | This is where you typically go if you want to install an app into a project you are 18 | working on. For an overview of all the Apps and Engines in the Toolkit App Store, 19 | click here: https://help.autodesk.com/view/SGDEV/ENU/?contextId=PC_TOOLKIT_APPS 20 | 21 | ## Have a Question? 22 | Don't hesitate to contact us at https://www.autodesk.com/support 23 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/ui/crash_dbg_form.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'crash_dbg_form.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 5.15.2 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from tank.platform.qt import QtCore 12 | for name, cls in QtCore.__dict__.items(): 13 | if isinstance(cls, type): globals()[name] = cls 14 | 15 | from tank.platform.qt import QtGui 16 | for name, cls in QtGui.__dict__.items(): 17 | if isinstance(cls, type): globals()[name] = cls 18 | 19 | 20 | class Ui_CrashDbgForm(object): 21 | def setupUi(self, CrashDbgForm): 22 | if not CrashDbgForm.objectName(): 23 | CrashDbgForm.setObjectName(u"CrashDbgForm") 24 | CrashDbgForm.resize(503, 395) 25 | self.verticalLayout = QVBoxLayout(CrashDbgForm) 26 | self.verticalLayout.setObjectName(u"verticalLayout") 27 | self.horizontalLayout = QHBoxLayout() 28 | self.horizontalLayout.setObjectName(u"horizontalLayout") 29 | self.tree_view = QTreeView(CrashDbgForm) 30 | self.tree_view.setObjectName(u"tree_view") 31 | 32 | self.horizontalLayout.addWidget(self.tree_view) 33 | 34 | self.list_view = QListView(CrashDbgForm) 35 | self.list_view.setObjectName(u"list_view") 36 | 37 | self.horizontalLayout.addWidget(self.list_view) 38 | 39 | self.verticalLayout.addLayout(self.horizontalLayout) 40 | 41 | self.retranslateUi(CrashDbgForm) 42 | 43 | QMetaObject.connectSlotsByName(CrashDbgForm) 44 | # setupUi 45 | 46 | def retranslateUi(self, CrashDbgForm): 47 | CrashDbgForm.setWindowTitle(QCoreApplication.translate("CrashDbgForm", u"Form", None)) 48 | # retranslateUi 49 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/sg_published_files_model.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ """ 12 | import sgtk 13 | 14 | shotgun_model = sgtk.platform.import_framework( 15 | "tk-framework-shotgunutils", "shotgun_model" 16 | ) 17 | ShotgunModel = shotgun_model.ShotgunModel 18 | 19 | 20 | class SgPublishedFilesModel(ShotgunModel): 21 | """ """ 22 | 23 | def __init__(self, uid, bg_task_manager, parent): 24 | """ """ 25 | ShotgunModel.__init__( 26 | self, parent, download_thumbs=False, bg_task_manager=bg_task_manager 27 | ) 28 | 29 | self._uid = uid 30 | 31 | # get the current published file type to use: 32 | app = sgtk.platform.current_bundle() 33 | self._published_file_type = sgtk.util.get_published_file_entity_type(app.sgtk) 34 | 35 | # @property 36 | def _get_uid(self): 37 | return self._uid 38 | 39 | def _set_uid(self, uid): 40 | self._uid = uid 41 | 42 | uid = property(_get_uid, _set_uid) 43 | 44 | def load_data(self, filters=None, fields=None): 45 | """ """ 46 | filters = filters or [] 47 | fields = fields or ["code"] 48 | hierarchy = [fields[0]] 49 | return self._load_data(self._published_file_type, filters, hierarchy, fields) 50 | 51 | def refresh(self): 52 | """ """ 53 | self._refresh_data() 54 | 55 | def get_sg_data(self): 56 | """ """ 57 | sg_data = [] 58 | for row in range(self.rowCount()): 59 | item = self.item(row, 0) 60 | sg_data.append(item.get_sg_data()) 61 | return sg_data 62 | -------------------------------------------------------------------------------- /tests/fixtures/configWF2ui/core/hooks/pick_environment.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2018 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ 12 | Hook which chooses an environment file to use based on the current context. 13 | """ 14 | 15 | from tank import Hook 16 | 17 | 18 | class PickEnvironment(Hook): 19 | def execute(self, context, **kwargs): 20 | """ 21 | The default implementation assumes there are three environments, called shot, asset 22 | and project, and switches to these based on entity type. 23 | """ 24 | if context.source_entity: 25 | if context.source_entity["type"] == "Version": 26 | return "version" 27 | elif context.source_entity["type"] == "PublishedFile": 28 | return "publishedfile" 29 | 30 | if context.project is None: 31 | # Our context is completely empty. We're going into the site context. 32 | return "site" 33 | 34 | if context.entity is None: 35 | # We have a project but not an entity. 36 | return "project" 37 | 38 | if context.entity and context.step is None: 39 | # We have an entity but no step. 40 | if context.entity["type"] == "Shot": 41 | return "shot" 42 | if context.entity["type"] == "Asset": 43 | return "asset" 44 | if context.entity["type"] == "Sequence": 45 | return "sequence" 46 | 47 | if context.entity and context.step: 48 | # We have a step and an entity. 49 | if context.entity["type"] == "Shot": 50 | return "shot_step" 51 | if context.entity["type"] == "Asset": 52 | return "asset_step" 53 | 54 | return None 55 | -------------------------------------------------------------------------------- /tests/fixtures/configWF2ui/core/templates.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # 12 | # This file is one of the central points in the Tank configuration and a counterpart to 13 | # the folder configuration structure. 14 | # 15 | # the folder structure underneath the project folder is used to create folders on disk - 16 | # templates.yml (this file) refers to those folders. Therefore, the two files need to be 17 | # in sync. This file contains an overview of all locations that are used in Tank. 18 | # 19 | # 20 | # Whenever an app or an engine refers to a location on disk, it is using a entry defined in 21 | # this file. For more information, see the Tank Documentation. 22 | 23 | keys: 24 | Step: 25 | type: str 26 | name: 27 | type: str 28 | version: 29 | type: int 30 | format_spec: "03" 31 | Task: 32 | type: str 33 | sg_asset_type: 34 | type: str 35 | Asset: 36 | type: str 37 | exclusions: [Seq, Shot] 38 | user: 39 | type: str 40 | shotgun_entity_type: HumanUser 41 | shotgun_field_name: login 42 | 43 | 44 | paths: 45 | 46 | # ------------------------------------------------------------------------------------ 47 | # Asset pipeline 48 | 49 | asset_root: assets/{sg_asset_type}/{Asset}/{Step} 50 | 51 | work_area: 52 | definition: '@asset_root/work/images' 53 | # define the location of a publish area 54 | publish_area: 55 | definition: '@asset_root/publish/images' 56 | 57 | # The location of published maya files 58 | publish_path: 59 | definition: '@publish_area/images/{name}.png' 60 | 61 | work_path: 62 | definition: '@work_area/images/{name}.png' 63 | 64 | strings: [] 65 | -------------------------------------------------------------------------------- /tests/fixtures/config/core/templates.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # 12 | # This file is one of the central points in the Tank configuration and a counterpart to 13 | # the folder configuration structure. 14 | # 15 | # the folder structure underneath the project folder is used to create folders on disk - 16 | # templates.yml (this file) refers to those folders. Therefore, the two files need to be 17 | # in sync. This file contains an overview of all locations that are used in Tank. 18 | # 19 | # 20 | # Whenever an app or an engine refers to a location on disk, it is using a entry defined in 21 | # this file. For more information, see the Tank Documentation. 22 | 23 | keys: 24 | Step: 25 | type: str 26 | name: 27 | type: str 28 | version: 29 | type: int 30 | format_spec: "03" 31 | Task: 32 | type: str 33 | sg_asset_type: 34 | type: str 35 | Asset: 36 | type: str 37 | exclusions: [Seq, Shot] 38 | user: 39 | type: str 40 | shotgun_entity_type: HumanUser 41 | shotgun_field_name: login 42 | 43 | 44 | paths: 45 | 46 | # ------------------------------------------------------------------------------------ 47 | # Asset pipeline 48 | 49 | asset_root: assets/{sg_asset_type}/{Asset}/{Step} 50 | 51 | work_area: 52 | definition: '@asset_root/work' 53 | # define the location of a publish area 54 | publish_area: 55 | definition: '@asset_root/publish' 56 | 57 | # The location of published maya files 58 | publish_path: 59 | definition: '@publish_area/{name}.v{version}.ma' 60 | 61 | task_path: 62 | definition: '@work_area/{Task}/{name}.v{version}.ma' 63 | 64 | sandbox_path: 65 | definition: '@work_area/{user}/{name}.v{version}.ma' 66 | 67 | strings: [] 68 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/actions/context_change_action.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ 12 | Action to create a change context. 13 | """ 14 | 15 | from sgtk import TankError 16 | from sgtk.platform.qt import QtGui 17 | 18 | from .file_action import FileAction 19 | from .action import Action 20 | 21 | 22 | class ContextChangeAction(Action): 23 | """ 24 | This action changes the current engine context. 25 | """ 26 | 27 | def __init__(self, environment): 28 | """ 29 | Constructor. 30 | 31 | :param environment: A WorkArea instance containing the context we will switch to. 32 | """ 33 | Action.__init__(self, "Change Context") 34 | self._environment = environment 35 | 36 | def execute(self, parent_ui): 37 | """ 38 | Perform a context change operation. 39 | 40 | :param parent_ui: Parent dialog executing this action. 41 | """ 42 | 43 | try: 44 | # create folders and validate that we can save using the work template: 45 | try: 46 | # create folders if needed: 47 | FileAction.create_folders(self._environment.context) 48 | 49 | except TankError: 50 | # log the original exception (useful for tracking down the problem) 51 | self._app.log_exception("Unable to run folder creation!") 52 | 53 | if not self._environment.context == self._app.context: 54 | # Change context 55 | FileAction.change_context(self._environment.context) 56 | 57 | except Exception as e: 58 | error_title = "Failed to complete '%s' action" % self.label 59 | QtGui.QMessageBox.information( 60 | parent_ui, error_title, "%s:\n\n%s" % (error_title, e) 61 | ) 62 | self._app.log_exception(error_title) 63 | return False 64 | 65 | return True 66 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/errors.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ 12 | Workfiles 2 related errors. 13 | """ 14 | 15 | from .work_area import WorkArea 16 | from sgtk import TankError 17 | 18 | 19 | class WorkfilesError(TankError): 20 | """ 21 | Base class for work area related errors. 22 | """ 23 | 24 | 25 | class MissingTemplatesError(WorkfilesError): 26 | """ 27 | Raised when one or more templates are missing. 28 | """ 29 | 30 | def __init__(self, missing_templates): 31 | """ 32 | Constructor. 33 | 34 | :param missing_templates: List of templates that are missing. 35 | """ 36 | WorkfilesError.__init__( 37 | self, self.generate_missing_templates_message(missing_templates) 38 | ) 39 | 40 | @classmethod 41 | def generate_missing_templates_message(self, missing_templates): 42 | """ 43 | Generates a warning for when templates are not all configured. 44 | """ 45 | if len(missing_templates) == WorkArea.NB_TEMPLATE_SETTINGS: 46 | return "No templates have been defined." 47 | else: 48 | # Then take every template except the last one and join them with commas. 49 | comma_separated_templates = missing_templates[:-1] 50 | comma_separated_string = ", ".join(comma_separated_templates) 51 | 52 | # If the string is not empty, we'll add the last missing template name. 53 | if comma_separated_string: 54 | missing_templates_string = "%s and %s" % ( 55 | comma_separated_string, 56 | missing_templates[-1], 57 | ) 58 | else: 59 | missing_templates_string = missing_templates[0] 60 | 61 | is_plural = len(missing_templates) > 1 62 | 63 | return "The template%s %s %s not been defined." % ( 64 | "s" if is_plural else "", 65 | missing_templates_string, 66 | "have" if is_plural else "has", 67 | ) 68 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/actions/show_in_shotgun_action.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ """ 12 | 13 | import sgtk 14 | from sgtk.platform.qt import QtGui, QtCore 15 | 16 | from .file_action import FileAction 17 | 18 | 19 | class ShowInShotgunAction(FileAction): 20 | """ """ 21 | 22 | def _open_url_for_published_file(self, file): 23 | """ """ 24 | # construct the url: 25 | published_file_entity_type = sgtk.util.get_published_file_entity_type( 26 | self._app.sgtk 27 | ) 28 | url = "%s/detail/%s/%d" % ( 29 | self._app.sgtk.shotgun.base_url, 30 | published_file_entity_type, 31 | file.published_file_id, 32 | ) 33 | 34 | # and open it: 35 | QtGui.QDesktopServices.openUrl(QtCore.QUrl(url)) 36 | 37 | 38 | class ShowPublishInShotgunAction(ShowInShotgunAction): 39 | """ """ 40 | 41 | def __init__(self, file, file_versions, environment): 42 | ShowInShotgunAction.__init__( 43 | self, 44 | "Show Publish in Flow Production Tracking", 45 | file, 46 | file_versions, 47 | environment, 48 | ) 49 | 50 | def execute(self, parent_ui): 51 | """ """ 52 | if not self.file or not self.file.is_published: 53 | return 54 | 55 | self._open_url_for_published_file(self.file) 56 | 57 | 58 | class ShowLatestPublishInShotgunAction(ShowInShotgunAction): 59 | """ """ 60 | 61 | def __init__(self, file, file_versions, environment): 62 | ShowInShotgunAction.__init__( 63 | self, 64 | "Show Latest Publish in Flow Production Tracking", 65 | file, 66 | file_versions, 67 | environment, 68 | ) 69 | 70 | def execute(self, parent_ui): 71 | """ """ 72 | publish_versions = [v for v, f in self.file_versions.items() if f.is_published] 73 | if not publish_versions: 74 | return 75 | 76 | max_publish_version = max(publish_versions) 77 | self._open_url_for_published_file(self.file_versions[max_publish_version]) 78 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/actions/action.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ 12 | Menu actions. 13 | """ 14 | 15 | import sgtk 16 | 17 | 18 | class ActionBase(object): 19 | """ 20 | Base class for Actions. 21 | """ 22 | 23 | def __init__(self, label): 24 | """ 25 | Constructor. 26 | 27 | :param label: Name of the action. 28 | """ 29 | self._app = sgtk.platform.current_bundle() 30 | self._label = label 31 | 32 | @property 33 | def label(self): 34 | """ 35 | :returns: The name of the action. 36 | """ 37 | return self._label 38 | 39 | 40 | class Action(ActionBase): 41 | """ 42 | Base class for leaf actions, ie, actions that when selected execute a piece of logic. This logic 43 | is implemented in the execute method. 44 | """ 45 | 46 | def execute(self, parent_ui): 47 | """ 48 | Called when the user executes a given action. The default implementation raises a NotImplementedError. 49 | 50 | :raises NotImplementedError: Thrown if a derived class doesn't implement this method and the client invokes it. 51 | """ 52 | raise NotImplementedError( 53 | "Implementation of _execute() method missing for action '%s'" % self.label 54 | ) 55 | 56 | 57 | class ActionGroup(ActionBase): 58 | """ 59 | Group of actions. 60 | """ 61 | 62 | def __init__(self, label, actions): 63 | """ 64 | Constructor. 65 | 66 | :param label: Name of the group of actions. 67 | :param actions: List of ActionBase actions. 68 | """ 69 | ActionBase.__init__(self, label) 70 | self.__actions = actions[:] 71 | 72 | @property 73 | def actions(self): 74 | """ 75 | :returns: List of ActionBase actions. 76 | """ 77 | return self.__actions 78 | 79 | 80 | class SeparatorAction(ActionBase): 81 | """ 82 | Not an actual action but a hint to the UI that a separation should be shown. 83 | """ 84 | 85 | def __init__(self): 86 | """ 87 | Constructor. 88 | """ 89 | ActionBase.__init__(self, "---") 90 | -------------------------------------------------------------------------------- /hooks/scene_operation_tk-mari.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | import sgtk 12 | 13 | HookClass = sgtk.get_hook_baseclass() 14 | 15 | 16 | class SceneOperation(HookClass): 17 | """ 18 | Hook called to perform an operation with the 19 | current scene 20 | """ 21 | 22 | def execute( 23 | self, 24 | operation, 25 | file_path, 26 | context, 27 | parent_action, 28 | file_version, 29 | read_only, 30 | **kwargs 31 | ): 32 | """ 33 | Main hook entry point 34 | 35 | :param operation: String 36 | Scene operation to perform 37 | 38 | :param file_path: String 39 | File path to use if the operation 40 | requires it (e.g. open) 41 | 42 | :param context: Context 43 | The context the file operation is being 44 | performed in. 45 | 46 | :param parent_action: This is the action that this scene operation is 47 | being executed for. This can be one of: 48 | - open_file 49 | - new_file 50 | - save_file_as 51 | - version_up 52 | 53 | :param file_version: The version/revision of the file to be opened. If this is 'None' 54 | then the latest version should be opened. 55 | 56 | :param read_only: Specifies if the file should be opened read-only or not 57 | 58 | :returns: Depends on operation: 59 | 'current_path' - Return the current scene 60 | file path as a String 61 | 'reset' - True if scene was reset to an empty 62 | state, otherwise False 63 | all others - None 64 | """ 65 | 66 | # Mari doesn't have any scene operations, since it only works with the context change mode. 67 | # However workfiles does require that it can find the hook, so this is a placeholder hook. 68 | pass 69 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/wrapper_dialog.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | import sys 12 | 13 | import sgtk 14 | from sgtk.platform.qt import QtCore, QtGui 15 | 16 | 17 | class WrapperDialog(QtGui.QDialog): 18 | @staticmethod 19 | def show_modal(widget, parent, title=None, fixed_size=None): 20 | dlg = WrapperDialog(widget, parent, title, fixed_size) 21 | try: 22 | return dlg.exec_() 23 | finally: 24 | dlg.clean_up() 25 | 26 | def __init__(self, widget, parent, title=None, fixed_size=None): 27 | QtGui.QDialog.__init__(self, parent) 28 | 29 | self._widget = widget 30 | self._widget.closeEvent = ( 31 | lambda event, dh=widget.closeEvent: self._handle_widget_close(event, dh) 32 | ) 33 | 34 | layout = QtGui.QVBoxLayout() 35 | layout.addWidget(self._widget) 36 | layout.setContentsMargins(0, 0, 0, 0) 37 | self.setLayout(layout) 38 | 39 | if title: 40 | self.setWindowTitle(title) 41 | if fixed_size: 42 | self.setFixedSize(fixed_size) 43 | 44 | def clean_up(self): 45 | # ensure that dialog is safey cleaned up when running nuke on a Mac 46 | if sgtk.util.is_macos() and sgtk.platform.current_engine().name == "tk-nuke": 47 | self.deleteLater() 48 | 49 | def __enter__(self): 50 | return self 51 | 52 | def __exit__(self, type, value, traceback): 53 | self.clean_up() 54 | 55 | def _handle_widget_close(self, event, default_handler): 56 | """ 57 | Callback from the hosted widget's closeEvent. 58 | Make sure that when a close() is issued for the hosted widget, 59 | the parent widget is closed too. 60 | """ 61 | # execute default handler and stop if not accepted: 62 | if default_handler: 63 | default_handler(event) 64 | if not event.isAccepted(): 65 | return 66 | else: 67 | event.accept() 68 | 69 | # use accepted as the default exit code 70 | exit_code = QtGui.QDialog.Accepted 71 | 72 | # look if the hosted widget has an exit_code we should pick up 73 | if hasattr(self._widget, "exit_code"): 74 | exit_code = self._widget.exit_code 75 | 76 | self.done(exit_code) 77 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/actions/save_as_file_action.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ """ 12 | import os 13 | from sgtk.platform.qt import QtCore, QtGui 14 | 15 | from .file_action import FileAction 16 | from ..scene_operation import save_file, SAVE_FILE_AS_ACTION 17 | 18 | 19 | class SaveAsFileAction(FileAction): 20 | """ """ 21 | 22 | def __init__(self, file_item, environment): 23 | """ """ 24 | FileAction.__init__(self, "Save As", file_item, None, environment) 25 | 26 | def execute(self, parent_ui): 27 | """ """ 28 | if ( 29 | not self.file 30 | or not self.file.path 31 | or not self.environment 32 | or not self.environment.context 33 | ): 34 | return False 35 | 36 | # switch context: 37 | previous_context = self._app.context 38 | if not self.environment.context == self._app.context: 39 | try: 40 | # Change the current context 41 | FileAction.change_context(self.environment.context) 42 | except Exception as e: 43 | QtGui.QMessageBox.critical( 44 | parent_ui, 45 | "Failed to change the work area", 46 | "Failed to change the work area to '%s':\n\n%s\n\nUnable to continue!" 47 | % (self.environment.context, e), 48 | ) 49 | self._app.log_exception( 50 | "Failed to change the work area to %s!" % self.environment.context 51 | ) 52 | return False 53 | 54 | # and save the current file as the new path: 55 | try: 56 | save_file( 57 | self._app, SAVE_FILE_AS_ACTION, self.environment.context, self.file.path 58 | ) 59 | except Exception as e: 60 | QtGui.QMessageBox.critical( 61 | None, "Failed to save file!", "Failed to save file:\n\n%s" % e 62 | ) 63 | self._app.log_exception("Failed to save file!") 64 | FileAction.restore_context(parent_ui, previous_context) 65 | return False 66 | 67 | try: 68 | self._app.log_metric("Saved Workfile") 69 | except: 70 | # ignore all errors. ex: using a core that doesn't support metrics 71 | pass 72 | 73 | return True 74 | -------------------------------------------------------------------------------- /hooks/filter_work_files.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | 12 | import sgtk 13 | 14 | HookClass = sgtk.get_hook_baseclass() 15 | 16 | 17 | class FilterWorkFiles(HookClass): 18 | """ 19 | Hook that can be used to filter the list of work files found by the app for the current 20 | Work area 21 | """ 22 | 23 | def execute(self, work_files, **kwargs): 24 | """ 25 | Main hook entry point 26 | 27 | :param work_files: List of dictionaries 28 | A list of dictionaries for the current work area within the app. Each 29 | item in the list is a Dictionary of the form: 30 | 31 | { 32 | "work_file" : { 33 | 34 | Dictionary containing information about a single work file. Valid entries in 35 | this dicitionary are listed below but may not be populated when the hook is 36 | executed! 37 | 38 | It is intended that custom versions of this hook should populate these fields 39 | if needed before returning the filtered list 40 | 41 | version_number - version of the work file 42 | name - name of the work file 43 | task - task the work file should be associated with 44 | description - description of the work file 45 | thumbnail - thumbnail that should be used for the work file 46 | modified_at - date & time the work file was last modified 47 | modified_by - Shotgun user entity dictionary for the person who 48 | last modified the work file 49 | } 50 | } 51 | 52 | 53 | :returns: The filtered list of dictionaries of the same form as the input 'work_files' 54 | list 55 | """ 56 | app = self.parent 57 | 58 | # the default implementation just returns the unfiltered list: 59 | return work_files 60 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/actions/new_task_action.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ """ 12 | 13 | import sgtk 14 | from sgtk.platform.qt import QtGui 15 | 16 | from .action import Action 17 | from ..new_task_form import NewTaskForm 18 | from ..user_cache import g_user_cache 19 | 20 | 21 | class NewTaskAction(Action): 22 | """ 23 | This action creates a new task for a given entity. 24 | """ 25 | 26 | def __init__(self, entity, step): 27 | """ 28 | Constructor. 29 | 30 | :param entity: Entity for which a task needs to be created. 31 | :param step: Default pipeline step for the new task. 32 | """ 33 | Action.__init__(self, "Create New Task") 34 | self._entity = entity 35 | self._step = step 36 | 37 | def execute(self, parent_ui): 38 | """ 39 | Shows the task creation form and creates the task. 40 | 41 | :param parent_ui: Parent widget for the dialog. 42 | 43 | :returns: If True, task creation was completed, returns False otherwise. 44 | """ 45 | if not self._entity: 46 | return False 47 | 48 | # show new task dialog: 49 | app = sgtk.platform.current_bundle() 50 | res, new_task_form = app.engine.show_modal( 51 | "Create New Task", 52 | app, 53 | NewTaskForm, 54 | self._entity, 55 | self._step, 56 | g_user_cache.current_user, 57 | parent_ui, 58 | ) 59 | 60 | if res != QtGui.QDialog.Accepted: 61 | return False 62 | 63 | try: 64 | from sgtk.util.metrics import EventMetric 65 | 66 | pipeline_step = new_task_form._get_pipeline_step() 67 | properties = { 68 | "Linked Entity Type": pipeline_step.get("type", "Unknown"), 69 | "Method": "Form", # since this was created from the Qt widget, 70 | "Task Name": pipeline_step.get("code", "unknown"), 71 | } 72 | 73 | # Log usage statistics about the Shotgun Desktop executable and the desktop startup. 74 | EventMetric.log( 75 | EventMetric.GROUP_TASKS, 76 | "Created Task", 77 | properties=properties, 78 | bundle=app, 79 | ) 80 | 81 | except ImportError as e: 82 | # ignore all errors. ex: using a core that doesn't support metrics 83 | pass 84 | 85 | return True 86 | -------------------------------------------------------------------------------- /tests/test_file_open_gui.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | import pytest 12 | from workfiles2_functions import _test_my_tasks_tab, _test_tab 13 | 14 | try: 15 | from MA.UI import topwindows 16 | except ImportError: 17 | pytestmark = pytest.mark.skip() 18 | 19 | 20 | @pytest.fixture(scope="module") 21 | def commands(): 22 | """ 23 | Return the command to launch Workfiles2 in different mode. 24 | This fixture is used by the host_application fixture in conftest.py 25 | """ 26 | return "file_open" 27 | 28 | 29 | @pytest.fixture(scope="module") 30 | def window_name(): 31 | """ 32 | Return the window app name. 33 | This fixture is used by the app_dialog fixture in conftest.py 34 | """ 35 | return "Flow Production Tracking: File Open" 36 | 37 | 38 | def test_my_tasks(app_dialog, tk_test_project): 39 | """ 40 | Basic My Tasks tab UI validation to make sure all buttons, tabs and fields are available 41 | """ 42 | # Validate the the right Workfiles 2 dialog mode is launched 43 | assert app_dialog.root.captions["File Open"].exists(), "Not the File Open dialog" 44 | 45 | # Validate File Open dialog buttons 46 | assert app_dialog.root.buttons[ 47 | "+ New File" 48 | ].exists(), "+ New File button is missing" 49 | assert app_dialog.root.buttons["Open"].exists(), "Open button is missing" 50 | 51 | # Validate File Open dialog checkboxes 52 | assert app_dialog.root.checkboxes[ 53 | "All Versions" 54 | ].exists(), "All Versions checkbox is missing" 55 | 56 | # My Tasks tab general UI validation 57 | _test_my_tasks_tab(app_dialog, tk_test_project) 58 | 59 | 60 | # Parametrize decorator to run the same functions for Assets and Shots tabs. 61 | @pytest.mark.parametrize( 62 | "tab_name, selection_hierarchy, entity, entity_type, step, task", 63 | [ 64 | ( 65 | "Assets", 66 | # Names of the tree items in the selection order 67 | ("Character", "AssetAutomation", "Model"), 68 | "Asset", 69 | "AssetAutomation", 70 | "Model", 71 | "Rig", 72 | ), 73 | ( 74 | "Shots", 75 | # Names of the tree items in the selection order 76 | ("seq_001", "shot_001", "Comp"), 77 | "Shot", 78 | "shot_001", 79 | "Comp", 80 | "Light", 81 | ), 82 | ], 83 | ) 84 | def test_tabs( 85 | app_dialog, tab_name, selection_hierarchy, entity, entity_type, step, task 86 | ): 87 | """ 88 | Assets/Shots tabs UI validation. 89 | """ 90 | _test_tab( 91 | app_dialog, tab_name, selection_hierarchy, entity, entity_type, step, task 92 | ) 93 | -------------------------------------------------------------------------------- /hooks/scene_operation_tk-houdini.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | import os 12 | import hou 13 | 14 | import sgtk 15 | 16 | HookClass = sgtk.get_hook_baseclass() 17 | 18 | 19 | class SceneOperation(HookClass): 20 | """ 21 | Hook called to perform an operation with the current scene 22 | """ 23 | 24 | def execute( 25 | self, 26 | operation, 27 | file_path, 28 | context, 29 | parent_action, 30 | file_version, 31 | read_only, 32 | **kwargs 33 | ): 34 | """ 35 | Main hook entry point 36 | 37 | :param operation: String 38 | Scene operation to perform 39 | 40 | :param file_path: String 41 | File path to use if the operation 42 | requires it (e.g. open) 43 | 44 | :param context: Context 45 | The context the file operation is being 46 | performed in. 47 | 48 | :param parent_action: This is the action that this scene operation is 49 | being executed for. This can be one of: 50 | - open_file 51 | - new_file 52 | - save_file_as 53 | - version_up 54 | 55 | :param file_version: The version/revision of the file to be opened. If this is 'None' 56 | then the latest version should be opened. 57 | 58 | :param read_only: Specifies if the file should be opened read-only or not 59 | 60 | :returns: Depends on operation: 61 | 'current_path' - Return the current scene 62 | file path as a String 63 | 'reset' - True if scene was reset to an empty 64 | state, otherwise False 65 | all others - None 66 | """ 67 | 68 | if operation == "current_path": 69 | return str(hou.hipFile.name()) 70 | elif operation == "open": 71 | # give houdini forward slashes 72 | file_path = file_path.replace(os.path.sep, "/") 73 | hou.hipFile.load(str(file_path)) 74 | elif operation == "save": 75 | hou.hipFile.save() 76 | elif operation == "save_as": 77 | # give houdini forward slashes 78 | file_path = file_path.replace(os.path.sep, "/") 79 | hou.hipFile.save(str(file_path)) 80 | elif operation == "reset": 81 | hou.hipFile.clear() 82 | return True 83 | -------------------------------------------------------------------------------- /tests/fixtures/configWF2ui/core/roots.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | # ------------------------------------------------------------------------------ 12 | # This file defines all the different disk locations that this config uses. 13 | # 14 | # Root definitions are referenced from the toolkit configuration's templates.yml 15 | # file and folder creation schema. 16 | # 17 | # At setup time, each defined root is looked up against the local storages 18 | # defined in shotgun. For each root defined in this file, a local storage needs 19 | # to exist in Shotgun. The `shotgun_storage_id` key in each root definition 20 | # below should correspond to a local storage in Shotgun. This allows the roots 21 | # defined here to be called anything, making it possible to reuse the 22 | # configuration without changing references to the storage root, only the 23 | # associated PTR storage id. If the `shotgun_storage_key` is not defined, then 24 | # the root name must match the name of the storage in PTR (legacy fallback). 25 | # 26 | # NOTE: Local storages ids can only be retrieved via the API currently: 27 | # 28 | # # get all local storages defined in PTR 29 | # shotgun.find("LocalStorage", [], ["code"]) 30 | # 31 | # One of the roots defined in this file should have a `default` key with a value 32 | # of `true` to identify the default storage root to use when none is specified 33 | # for a path template. If no roots are identified a `default`, then one root 34 | # must be named `primary` which will indicate the default root. 35 | # 36 | # During setup, the paths defined for each local storage are transferred across 37 | # to this file. 38 | # 39 | # Note: In toolkit core versions prior to 0.18.121, configurations using a 40 | # single root required this root to be named 'primary'. In later versions of 41 | # core, this requirement has been lifted and the root can be named anything. 42 | # ------------------------------------------------------------------------------ 43 | 44 | Toolkit UI Automation: 45 | 46 | description: A top-level root folder for production data. Project folders 47 | will be created beneath this location. 48 | 49 | # these paths will be populated by the associated local storage in Shotgun 50 | # at setup time. 51 | mac_path: 52 | linux_path: 53 | windows_path: 54 | 55 | # indicates that this is the default storage root 56 | default: true 57 | 58 | # ------------------------------------------------------------------------------ 59 | # Below is an example of a second storage root. This root would require a local 60 | # storage defined in Shotgun with an id of 2. 61 | # 62 | # textures: 63 | # mac_path: /studio/textures 64 | # linux_path: /studio/textures 65 | # windows_path: 66 | # 67 | # description: "High performance storage for fast/frequent texture access" 68 | # shotgun_storage_id: 2 69 | # 70 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/entity_tree/entity_tree_proxy_model.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ """ 12 | 13 | import sgtk 14 | from ..entity_proxy_model import EntityProxyModel 15 | from sgtk.platform.qt import QtCore 16 | 17 | from ..user_cache import g_user_cache 18 | 19 | 20 | class EntityTreeProxyModel(EntityProxyModel): 21 | """ 22 | Proxy model that handles searching and sorting of the 23 | left hand side entity hierarchies. 24 | """ 25 | 26 | def __init__(self, parent, compare_sg_fields): 27 | """ """ 28 | EntityProxyModel.__init__(self, parent, compare_sg_fields) 29 | self._only_show_my_tasks = False 30 | self.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) 31 | 32 | # @property 33 | def _get_only_show_my_tasks(self): 34 | return self._only_show_my_tasks 35 | 36 | # @only_show_my_tasks.setter 37 | def _set_only_show_my_tasks(self, show): 38 | if self._only_show_my_tasks != show: 39 | # We're forcing a load of the model's data here to ensure we have 40 | # everything in memory that's required to properly filter the tree. 41 | # Since this my tasks checkbox feature is only available when we're 42 | # NOT using a deferred-query model, everything we need from Shotgun 43 | # is already here and we just need to populate the model with the 44 | # full tree of items prior to filtering. 45 | self.sourceModel().ensure_data_is_loaded() 46 | self._only_show_my_tasks = show 47 | self.invalidateFilter() 48 | 49 | only_show_my_tasks = property(_get_only_show_my_tasks, _set_only_show_my_tasks) 50 | 51 | def _is_row_accepted(self, src_row, src_parent_idx, parent_accepted): 52 | """ """ 53 | if self._only_show_my_tasks: 54 | # filter out any tasks that aren't assigned to the current user: 55 | current_user = g_user_cache.current_user 56 | if not current_user: 57 | return False 58 | 59 | src_idx = self.sourceModel().index(src_row, 0, src_parent_idx) 60 | if not src_idx.isValid(): 61 | return False 62 | 63 | item = src_idx.model().itemFromIndex(src_idx) 64 | sg_entity = src_idx.model().get_entity(item) 65 | 66 | if not sg_entity or sg_entity["type"] != "Task": 67 | return False 68 | 69 | assignees = sg_entity.get("task_assignees", []) 70 | assignee_ids = [a["id"] for a in assignees if "id" in a] 71 | if current_user["id"] not in assignee_ids: 72 | return False 73 | 74 | # we accept this row so lets check with the base implementation: 75 | return EntityProxyModel._is_row_accepted( 76 | self, src_row, src_parent_idx, parent_accepted 77 | ) 78 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/framework_qtwidgets.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ 12 | Wrapper for the various widgets used from frameworks so that they can be used 13 | easily from within Qt Designer 14 | """ 15 | 16 | import sgtk 17 | 18 | # search widget: 19 | search_widget = sgtk.platform.import_framework( 20 | "tk-framework-qtwidgets", "search_widget" 21 | ) 22 | SearchWidget = search_widget.SearchWidget 23 | 24 | # elided text label: 25 | elided_label = sgtk.platform.import_framework("tk-framework-qtwidgets", "elided_label") 26 | ElidedLabel = elided_label.ElidedLabel 27 | 28 | # navigation and breadcrumb controls: 29 | navigation = sgtk.platform.import_framework("tk-framework-qtwidgets", "navigation") 30 | NavigationWidget = navigation.NavigationWidget 31 | BreadcrumbWidget = navigation.BreadcrumbWidget 32 | Breadcrumb = navigation.Breadcrumb 33 | 34 | # Grouped list view, widget base class and delegates: 35 | views = sgtk.platform.import_framework("tk-framework-qtwidgets", "views") 36 | GroupedItemView = views.GroupedItemView 37 | 38 | # hierarchical filtering proxy model: 39 | models = sgtk.platform.import_framework("tk-framework-qtwidgets", "models") 40 | HierarchicalFilteringProxyModel = models.HierarchicalFilteringProxyModel 41 | 42 | overlay_widget = sgtk.platform.import_framework( 43 | "tk-framework-qtwidgets", "overlay_widget" 44 | ) 45 | 46 | filtering = sgtk.platform.import_framework("tk-framework-qtwidgets", "filtering") 47 | FilterMenu = filtering.FilterMenu 48 | FilterMenuButton = filtering.FilterMenuButton 49 | FilterItemTreeProxyModel = filtering.FilterItemTreeProxyModel 50 | delegates = sgtk.platform.import_framework("tk-framework-qtwidgets", "delegates") 51 | ViewItemDelegate = delegates.ViewItemDelegate 52 | 53 | sg_qicons = sgtk.platform.import_framework("tk-framework-qtwidgets", "sg_qicons") 54 | 55 | shotgun_menus = sgtk.platform.import_framework( 56 | "tk-framework-qtwidgets", "shotgun_menus" 57 | ) 58 | ShotgunMenu = shotgun_menus.ShotgunMenu 59 | 60 | message_box = sgtk.platform.import_framework("tk-framework-qtwidgets", "message_box") 61 | MessageBox = message_box.MessageBox 62 | 63 | shotgun_fields = sgtk.platform.import_framework( 64 | "tk-framework-qtwidgets", "shotgun_fields" 65 | ) 66 | sg_qwidgets = sgtk.platform.import_framework("tk-framework-qtwidgets", "sg_qwidgets") 67 | 68 | 69 | class SGQIcon(sg_qicons.SGQIcon): 70 | """ 71 | A wrapper for the SGQIcon class to include additional icon resources. 72 | """ 73 | 74 | @classmethod 75 | def sort_asc(cls): 76 | return cls(cls.resource_version_details_path("sort_up")) 77 | 78 | @classmethod 79 | def sort_desc(cls): 80 | return cls(cls.resource_version_details_path("sort_down")) 81 | 82 | @classmethod 83 | def resource_version_details_path(cls, name, ext="png"): 84 | return ":/version_details/{icon_name}.{ext}".format( 85 | icon_name=name, 86 | ext=ext, 87 | ) 88 | -------------------------------------------------------------------------------- /hooks/scene_operation_tk-3dsmaxplus.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | 12 | import MaxPlus 13 | import sgtk 14 | 15 | HookClass = sgtk.get_hook_baseclass() 16 | 17 | 18 | class SceneOperation(HookClass): 19 | """ 20 | Hook called to perform an operation with the current scene 21 | """ 22 | 23 | def execute( 24 | self, 25 | operation, 26 | file_path, 27 | context, 28 | parent_action, 29 | file_version, 30 | read_only, 31 | **kwargs 32 | ): 33 | """ 34 | Main hook entry point 35 | 36 | :param operation: String 37 | Scene operation to perform 38 | 39 | :param file_path: String 40 | File path to use if the operation 41 | requires it (e.g. open) 42 | 43 | :param context: Context 44 | The context the file operation is being 45 | performed in. 46 | 47 | :param parent_action: This is the action that this scene operation is 48 | being executed for. This can be one of: 49 | - open_file 50 | - new_file 51 | - save_file_as 52 | - version_up 53 | 54 | :param file_version: The version/revision of the file to be opened. If this is 'None' 55 | then the latest version should be opened. 56 | 57 | :param read_only: Specifies if the file should be opened read-only or not 58 | 59 | :returns: Depends on operation: 60 | 'current_path' - Return the current scene 61 | file path as a String 62 | 'reset' - True if scene was reset to an empty 63 | state, otherwise False 64 | all others - None 65 | """ 66 | if operation == "current_path": 67 | # return the current scene path 68 | file_path = MaxPlus.FileManager.GetFileNameAndPath() 69 | if not file_path: 70 | return "" 71 | return file_path 72 | elif operation == "open": 73 | # open the specified scene 74 | MaxPlus.FileManager.Open(file_path) 75 | elif operation == "save": 76 | # save the current scene: 77 | MaxPlus.FileManager.Save() 78 | elif operation == "save_as": 79 | # save the scene as file_path: 80 | MaxPlus.FileManager.Save(file_path) 81 | elif operation == "reset": 82 | """ 83 | Reset the scene to an empty state 84 | """ 85 | MaxPlus.FileManager.Reset(True) 86 | return True 87 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/context_change_form.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ 12 | Qt widget that presents the user with a list of entities 13 | so that they can choose a context to work in. 14 | """ 15 | import sgtk 16 | 17 | from .actions.context_change_action import ContextChangeAction 18 | from .file_form_base import FileFormBase 19 | from .ui.file_open_form import Ui_FileOpenForm 20 | 21 | 22 | class ContextChangeForm(FileFormBase): 23 | def __init__(self, parent=None): 24 | super().__init__(parent) 25 | 26 | self._context_change_env = None 27 | 28 | try: 29 | # doing this inside a try-except to ensure any exceptions raised don't 30 | # break the UI and crash the dcc horribly! 31 | self._do_init() 32 | except Exception: 33 | app = sgtk.platform.current_bundle() 34 | app.log_exception("Unhandled exception during Form construction!") 35 | 36 | def init_ui_file(self): 37 | """ 38 | Returns the ui class to use, required by the base class. 39 | """ 40 | return Ui_FileOpenForm() 41 | 42 | def _do_init(self): 43 | """ """ 44 | super()._do_init() 45 | 46 | # start by disabling buttons: 47 | self._ui.open_btn.hide() 48 | self._ui.new_file_btn.hide() 49 | self._ui.change_ctx_btn.setEnabled(False) 50 | self._ui.open_options_btn.hide() 51 | 52 | # hook up signals on controls: 53 | self._ui.change_ctx_btn.clicked.connect(self._on_context_change) 54 | self._ui.browser.task_double_clicked.connect(self._on_context_change) 55 | 56 | self._ui.browser.set_models( 57 | self._my_tasks_model, 58 | self._entity_models, 59 | None, 60 | ) 61 | 62 | # initialize the browser widget: 63 | app = sgtk.platform.current_bundle() 64 | self._ui.browser.select_work_area(app.context) 65 | 66 | def _on_browser_work_area_changed(self, entity, breadcrumbs): 67 | """ 68 | Slot triggered whenever the work area is changed in the browser. 69 | """ 70 | env_details = super()._on_browser_work_area_changed(entity, breadcrumbs) 71 | 72 | self._update_change_context_btn(env_details) 73 | 74 | def _update_change_context_btn(self, env): 75 | """ 76 | Updates the current selected context, and updates the context change button state. 77 | When no environment is gathered from the context change button is disabled. 78 | """ 79 | self._context_change_env = env 80 | if env: 81 | self._ui.change_ctx_btn.setEnabled(True) 82 | else: 83 | self._ui.change_ctx_btn.setEnabled(False) 84 | 85 | def _on_context_change(self, *_args): 86 | """ 87 | Calls the context change action which will change the current engine's context and close the dialog. 88 | """ 89 | context_change_action = ContextChangeAction(self._context_change_env) 90 | 91 | self._perform_action(context_change_action) 92 | -------------------------------------------------------------------------------- /hooks/scene_operation_tk-shell.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | import os 12 | 13 | import sgtk 14 | from sgtk.platform.qt import QtGui 15 | 16 | HookClass = sgtk.get_hook_baseclass() 17 | 18 | 19 | class SceneOperation(HookClass): 20 | """ 21 | Hook called to perform an operation with the current scene. 22 | """ 23 | 24 | def execute( 25 | self, 26 | operation, 27 | file_path, 28 | context, 29 | parent_action, 30 | file_version, 31 | read_only, 32 | **kwargs 33 | ): 34 | """ 35 | Main hook entry point. 36 | 37 | :param operation: String 38 | Scene operation to perform 39 | 40 | :param file_path: String 41 | File path to use if the operation 42 | requires it (e.g. open) 43 | 44 | :param context: Context 45 | The context the file operation is being 46 | performed in. 47 | 48 | :param parent_action: This is the action that this scene operation is 49 | being executed for. This can be one of: 50 | - open_file 51 | - new_file 52 | - save_file_as 53 | - version_up 54 | 55 | :param file_version: The version/revision of the file to be opened. If this is 'None' 56 | then the latest version should be opened. 57 | 58 | :param read_only: Specifies if the file should be opened read-only or not 59 | 60 | :returns: Depends on operation: 61 | 'current_path' - Return the current scene 62 | file path as a String 63 | 'reset' - True if scene was reset to an empty 64 | state, otherwise False 65 | all others - None 66 | """ 67 | engine = self.parent.engine 68 | 69 | engine.log_debug("scene_operation.execute.%s" % operation) 70 | engine.log_debug(" file_path: %s" % file_path) 71 | engine.log_debug(" context: %s" % context) 72 | engine.log_debug(" parent_action: %s" % parent_action) 73 | engine.log_debug(" file_version: %s" % file_version) 74 | engine.log_debug(" read_only: %s" % read_only) 75 | 76 | if operation == "current_path": 77 | return os.getcwd() 78 | elif operation == "reset": 79 | return True 80 | elif operation == "open": 81 | return ( 82 | QtGui.QMessageBox.question( 83 | None, 84 | "", 85 | "Are you sure you want to open?", 86 | QtGui.QMessageBox.Yes | QtGui.QMessageBox.No, 87 | ) 88 | == QtGui.QMessageBox.Yes 89 | ) 90 | -------------------------------------------------------------------------------- /hooks/create_new_task.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ 12 | Task creation hook. 13 | """ 14 | 15 | import sgtk 16 | 17 | HookClass = sgtk.get_hook_baseclass() 18 | 19 | 20 | class CreateNewTaskHook(HookClass): 21 | """ 22 | Hook called to create a task for a given entity and step. 23 | """ 24 | 25 | def create_task_name_validator(self): 26 | """ 27 | Create a QtGui.QValidator instance that will be used by the task name field to interactively 28 | inform if the name is valid or not. The caller will take ownership of the validator. 29 | 30 | For example, this simple validator will prevent the user from entering spaces in the field. 31 | 32 | .. code-block:: python 33 | 34 | class _Validator(QtGui.QValidator): 35 | 36 | def validate(self, text, pos): 37 | if " " in text: 38 | return QtGui.QValidator.Intermediate, text.replace(" ", ""), pos - 1 39 | else: 40 | return QtGui.QValidator.Acceptable, text, pos 41 | 42 | .. note:: The calling convention for fixup and validate have been modified to make them more pythonic. 43 | http://pyside.readthedocs.org/en/1.2.2/sources/pyside/doc/pysideapi2.html?highlight=string#qstring 44 | 45 | :returns: A QtGui.QValidator derived object. 46 | """ 47 | return None 48 | 49 | def create_new_task(self, name, pipeline_step, entity, assigned_to=None): 50 | """ 51 | Create a new task with the specified information. 52 | 53 | :param name: Name of the task to be created. 54 | 55 | :param pipeline_step: Pipeline step associated with the task. 56 | :type pipeline_step: dictionary with keys 'Type' and 'id' 57 | 58 | :param entity: Entity associated with this task. 59 | :type entity: dictionary with keys 'Type' and 'id' 60 | 61 | :param assigned_to: User assigned to the task. Can be None. 62 | :type assigned_to: dictionary with keys 'Type' and 'id' 63 | 64 | :returns: The created task. 65 | :rtype: dictionary with keys 'step', 'project', 'entity', 'content' and 'task_assignees' if 66 | 'assigned_to' was set. 67 | 68 | :raises sgtk.TankError: On error, during validation or creation, this method 69 | raises a TankError. 70 | """ 71 | app = self.parent 72 | 73 | # construct the data for the new Task entity: 74 | data = { 75 | "step": pipeline_step, 76 | "project": app.context.project, 77 | "entity": entity, 78 | "content": name, 79 | } 80 | if assigned_to: 81 | data["task_assignees"] = [assigned_to] 82 | 83 | # create the task: 84 | sg_result = app.shotgun.create("Task", data) 85 | if not sg_result: 86 | raise sgtk.TankError("Failed to create new task - reason unknown!") 87 | 88 | # try to set it to IP - not all studios use IP so be careful! 89 | try: 90 | app.shotgun.update("Task", sg_result["id"], {"sg_status_list": "ip"}) 91 | except: 92 | pass 93 | 94 | return sg_result 95 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/actions/open_publish_actions.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ """ 12 | import sgtk 13 | from sgtk.platform.qt import QtCore, QtGui 14 | from sgtk import TankError 15 | 16 | from .open_file_action import ( 17 | OpenFileAction, 18 | CopyAndOpenInCurrentWorkAreaAction, 19 | ContinueFromFileAction, 20 | ) 21 | 22 | from ..user_cache import g_user_cache 23 | 24 | 25 | class OpenPublishAction(OpenFileAction): 26 | """ """ 27 | 28 | def __init__(self, file, file_versions, environment): 29 | """ """ 30 | all_versions = [v for v, f in file_versions.items()] 31 | max_version = max(all_versions) if all_versions else 0 32 | 33 | label = "" 34 | if file.version == max_version: 35 | label = "Open from the Publish Area" 36 | else: 37 | label = "Open v%03d from the Publish Area" % file.version 38 | 39 | OpenFileAction.__init__(self, label, file, file_versions, environment) 40 | 41 | def execute(self, parent_ui): 42 | """ """ 43 | if not self.file or not self.file.is_published: 44 | return False 45 | 46 | return self._do_copy_and_open( 47 | src_path=None, 48 | dst_path=self.file.publish_path, 49 | version=self.file.version, 50 | read_only=self.file.editable, 51 | new_ctx=self.environment.context, 52 | parent_ui=parent_ui, 53 | check_refs=False, 54 | ) 55 | 56 | 57 | class ContinueFromPublishAction(ContinueFromFileAction): 58 | """ """ 59 | 60 | def __init__(self, file, file_versions, environment): 61 | """ """ 62 | ContinueFromFileAction.__init__( 63 | self, "Continue Working From Publish", file, file_versions, environment 64 | ) 65 | 66 | def execute(self, parent_ui): 67 | """ """ 68 | if not self.file.is_published or not self.environment.publish_template: 69 | return False 70 | 71 | # source path is the file publish path: 72 | src_path = self.file.publish_path 73 | 74 | return self._continue_from( 75 | src_path, self.environment.publish_template, parent_ui 76 | ) 77 | 78 | 79 | class CopyAndOpenPublishInCurrentWorkAreaAction(CopyAndOpenInCurrentWorkAreaAction): 80 | """ """ 81 | 82 | def __init__(self, file, file_versions, environment): 83 | CopyAndOpenInCurrentWorkAreaAction.__init__( 84 | self, 85 | "Open Publish in Current Work Area...", 86 | file, 87 | file_versions, 88 | environment, 89 | ) 90 | 91 | def execute(self, parent_ui): 92 | """ """ 93 | if ( 94 | not self.file 95 | or not self.file.is_published 96 | or not self.environment.publish_template 97 | ): 98 | return False 99 | 100 | return self._open_in_current_work_area( 101 | self.file.publish_path, 102 | self.environment.publish_template, 103 | self.file, 104 | self.environment, 105 | parent_ui, 106 | ) 107 | -------------------------------------------------------------------------------- /tests/test_file_save_gui.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | import pytest 12 | from workfiles2_functions import _test_my_tasks_tab, _test_tab 13 | 14 | try: 15 | from MA.UI import topwindows 16 | except ImportError: 17 | pytestmark = pytest.mark.skip() 18 | 19 | 20 | @pytest.fixture(scope="module") 21 | def commands(): 22 | """ 23 | Return the command to launch Workfiles2 in different mode. 24 | This fixture is used by the host_application fixture in conftest.py 25 | """ 26 | return "file_save" 27 | 28 | 29 | @pytest.fixture(scope="module") 30 | def window_name(): 31 | """ 32 | Return the window app name. 33 | This fixture is used by the app_dialog fixture in conftest.py 34 | """ 35 | return "Flow Production Tracking: File Save" 36 | 37 | 38 | def test_my_tasks(app_dialog, tk_test_project): 39 | """ 40 | Basic My Tasks tab UI validation to make sure all buttons, tabs and fields are available 41 | """ 42 | # Validate that the right Workfiles 2 dialog mode is launched 43 | assert app_dialog.root.captions["File Save"].exists(), "Not the File Open dialog" 44 | 45 | # Validate File Save dialog buttons 46 | assert app_dialog.root.dropdowns[ 47 | "File Type" 48 | ].exists(), "Open file type drop down menu is missing" 49 | assert app_dialog.root.buttons["Save"].exists(), "Save button is missing" 50 | 51 | # Validate File Save dialog text fields 52 | assert app_dialog.root.textfields[ 53 | "Name Edit" 54 | ].exists(), "Name text field is missing" 55 | assert app_dialog.root["Version Number"].exists(), "Version text field is missing" 56 | 57 | # Validate File Save dialog checkboxes 58 | assert app_dialog.root.checkboxes[ 59 | "Use Next Available Version Number" 60 | ].exists(), "Use Next Available Version Number checkbox is missing" 61 | assert app_dialog.root.checkboxes[ 62 | "Use Next Available Version Number" 63 | ].checked, "Use Next Available Version Number checkbox should be checked by default" 64 | 65 | # My Tasks tab general UI validation 66 | _test_my_tasks_tab(app_dialog, tk_test_project) 67 | 68 | 69 | # Parametrize decorator to run the same functions for Assets and Shots tabs. 70 | @pytest.mark.parametrize( 71 | "tab_name, selection_hierarchy, entity, entity_type, step, task", 72 | [ 73 | ( 74 | "Assets", 75 | # Names of the tree items in the selection order 76 | ("Character", "AssetAutomation", "Model"), 77 | "Asset", 78 | "AssetAutomation", 79 | "Model", 80 | "Rig", 81 | ), 82 | ( 83 | "Shots", 84 | # Names of the tree items in the selection order 85 | ("seq_001", "shot_001", "Comp"), 86 | "Shot", 87 | "shot_001", 88 | "Comp", 89 | "Light", 90 | ), 91 | ], 92 | ) 93 | def test_tabs( 94 | app_dialog, tab_name, selection_hierarchy, entity, entity_type, step, task 95 | ): 96 | """ 97 | Assets/Shots tabs UI validation. 98 | """ 99 | _test_tab( 100 | app_dialog, tab_name, selection_hierarchy, entity, entity_type, step, task 101 | ) 102 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/ui/my_tasks_form.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'my_tasks_form.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 5.15.2 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from tank.platform.qt import QtCore 12 | for name, cls in QtCore.__dict__.items(): 13 | if isinstance(cls, type): globals()[name] = cls 14 | 15 | from tank.platform.qt import QtGui 16 | for name, cls in QtGui.__dict__.items(): 17 | if isinstance(cls, type): globals()[name] = cls 18 | 19 | 20 | from ..framework_qtwidgets import SearchWidget 21 | 22 | class Ui_MyTasksForm(object): 23 | def setupUi(self, MyTasksForm): 24 | if not MyTasksForm.objectName(): 25 | MyTasksForm.setObjectName(u"MyTasksForm") 26 | MyTasksForm.resize(359, 541) 27 | self.verticalLayout = QVBoxLayout(MyTasksForm) 28 | self.verticalLayout.setSpacing(4) 29 | self.verticalLayout.setObjectName(u"verticalLayout") 30 | self.verticalLayout.setContentsMargins(2, 6, 2, 2) 31 | self.horizontalLayout = QHBoxLayout() 32 | self.horizontalLayout.setObjectName(u"horizontalLayout") 33 | self.horizontalLayout.setContentsMargins(2, -1, 2, 1) 34 | self.filter_btn = QToolButton(MyTasksForm) 35 | self.filter_btn.setObjectName(u"filter_btn") 36 | self.filter_btn.setStyleSheet(u"") 37 | self.filter_btn.setPopupMode(QToolButton.MenuButtonPopup) 38 | self.filter_btn.setToolButtonStyle(Qt.ToolButtonTextBesideIcon) 39 | self.filter_btn.setAutoRaise(False) 40 | 41 | self.horizontalLayout.addWidget(self.filter_btn) 42 | 43 | self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) 44 | 45 | self.horizontalLayout.addItem(self.horizontalSpacer) 46 | 47 | self.new_task_btn = QPushButton(MyTasksForm) 48 | self.new_task_btn.setObjectName(u"new_task_btn") 49 | 50 | self.horizontalLayout.addWidget(self.new_task_btn) 51 | 52 | self.verticalLayout.addLayout(self.horizontalLayout) 53 | 54 | self.horizontalLayout_2 = QHBoxLayout() 55 | self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") 56 | self.horizontalLayout_2.setContentsMargins(1, -1, 1, -1) 57 | self.search_ctrl = SearchWidget(MyTasksForm) 58 | self.search_ctrl.setObjectName(u"search_ctrl") 59 | self.search_ctrl.setMinimumSize(QSize(0, 20)) 60 | self.search_ctrl.setStyleSheet(u"#search_ctrl {\n" 61 | "background-color: rgb(255, 128, 0);\n" 62 | "}") 63 | 64 | self.horizontalLayout_2.addWidget(self.search_ctrl) 65 | 66 | self.verticalLayout.addLayout(self.horizontalLayout_2) 67 | 68 | self.task_tree = QTreeView(MyTasksForm) 69 | self.task_tree.setObjectName(u"task_tree") 70 | self.task_tree.setEditTriggers(QAbstractItemView.NoEditTriggers) 71 | self.task_tree.setProperty("showDropIndicator", False) 72 | self.task_tree.setRootIsDecorated(False) 73 | self.task_tree.header().setVisible(False) 74 | 75 | self.verticalLayout.addWidget(self.task_tree) 76 | 77 | self.verticalLayout.setStretch(2, 1) 78 | 79 | self.retranslateUi(MyTasksForm) 80 | 81 | QMetaObject.connectSlotsByName(MyTasksForm) 82 | # setupUi 83 | 84 | def retranslateUi(self, MyTasksForm): 85 | MyTasksForm.setWindowTitle(QCoreApplication.translate("MyTasksForm", u"Form", None)) 86 | self.filter_btn.setText(QCoreApplication.translate("MyTasksForm", u"Filter", None)) 87 | self.new_task_btn.setText(QCoreApplication.translate("MyTasksForm", u"+ New Task", None)) 88 | # retranslateUi 89 | -------------------------------------------------------------------------------- /hooks/get_badge.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2019 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | 12 | import sgtk 13 | from sgtk.platform.qt import QtGui 14 | 15 | HookClass = sgtk.get_hook_baseclass() 16 | 17 | 18 | class GetBadge(HookClass): 19 | """ 20 | Hook that can be used to generate badges for display on publishes and work files. 21 | """ 22 | 23 | def get_publish_badge(self, publish_details, publish_path, **kwargs): 24 | """ 25 | Generate a badge for a publish. 26 | 27 | :param dict publish_details: A dictionary for the publish to generate a badge for. 28 | Keys may include: 29 | - task 30 | - modified_by 31 | - name 32 | - modified_at 33 | - published_at 34 | - thumbnail 35 | - publish_description 36 | - published_by 37 | - version 38 | - entity 39 | 40 | :param str publish_path: The path to the publish on disk. 41 | 42 | 43 | 44 | :returns: A QPixmap or QColor to use for the badge, if a badge should be applied, otherwise 45 | None. If a QColor is returned, a circular "dot" badge will be generated using that 46 | color. 47 | """ 48 | # the default implementation always returns None. 49 | return None 50 | 51 | def get_work_file_badge(self, work_file_details, work_file_path, **kwargs): 52 | """ 53 | Generate a badge for a work file. 54 | 55 | :param dict work_file_details: A dictionary for the work file to generate a badge for. 56 | Keys may include: 57 | - task 58 | - step 59 | - modified_by 60 | - name 61 | - modified_at 62 | - entity 63 | - version 64 | - thumbnail 65 | - editable 66 | - editable_reason 67 | 68 | :param str work_file_path: The path to the work file on disk. 69 | 70 | 71 | :returns: A QPixmap or QColor to use for the badge, if a badge should be applied, otherwise 72 | None. If a QColor is returned, a circular "dot" badge will be generated using that 73 | color. 74 | """ 75 | # the default implementation always returns None. 76 | return None 77 | 78 | def generate_badge_pixmap(self, badge_color): 79 | """ 80 | Generate a badge QPixmap from a QColor. This hook method is used to generate a badge image 81 | when a badge hook returns a QColor. Thus, by overloading this method, it's possible to 82 | customize what the generated badges will look like when get_work_file_badge or 83 | get_publish_badge return a QColor. 84 | 85 | :param QColor badge_color: The color of the badge to generate a pixmap for. 86 | 87 | :returns: A QPixmap of the badge to be used. 88 | """ 89 | # We want to multiply the color onto the (white) badge_default dot to 90 | # generate a nice looking badge. 91 | badge = QtGui.QPixmap(":/tk-multi-workfiles2/badge_default.png") 92 | painter = QtGui.QPainter() 93 | painter.begin(badge) 94 | try: 95 | painter.setCompositionMode(QtGui.QPainter.CompositionMode_SourceIn) 96 | painter.fillRect(badge.rect(), badge_color) 97 | finally: 98 | painter.end() 99 | return badge 100 | -------------------------------------------------------------------------------- /hooks/scene_operation_tk-photoshopcc.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | import os 12 | 13 | import sgtk 14 | from sgtk import TankError 15 | 16 | HookClass = sgtk.get_hook_baseclass() 17 | 18 | 19 | class SceneOperation(HookClass): 20 | """ 21 | Hook called to perform an operation with the 22 | current scene 23 | """ 24 | 25 | def execute( 26 | self, 27 | operation, 28 | file_path, 29 | context, 30 | parent_action, 31 | file_version, 32 | read_only, 33 | **kwargs 34 | ): 35 | """ 36 | Main hook entry point 37 | 38 | :param operation: String 39 | Scene operation to perform 40 | 41 | :param file_path: String 42 | File path to use if the operation 43 | requires it (e.g. open) 44 | 45 | :param context: Context 46 | The context the file operation is being 47 | performed in. 48 | 49 | :param parent_action: This is the action that this scene operation is 50 | being executed for. This can be one of: 51 | - open_file 52 | - new_file 53 | - save_file_as 54 | - version_up 55 | 56 | :param file_version: The version/revision of the file to be opened. If this is 'None' 57 | then the latest version should be opened. 58 | 59 | :param read_only: Specifies if the file should be opened read-only or not 60 | 61 | :returns: Depends on operation: 62 | 'current_path' - Return the current scene 63 | file path as a String 64 | 'reset' - True if scene was reset to an empty 65 | state, otherwise False 66 | all others - None 67 | """ 68 | adobe = self.parent.engine.adobe 69 | 70 | if operation == "current_path": 71 | # return the current script path 72 | return adobe.get_active_document_path() or "" 73 | 74 | elif operation == "open": 75 | # open the specified script 76 | adobe.app.load(adobe.File(file_path)) 77 | 78 | elif operation == "save": 79 | # save the current script: 80 | doc = self._get_active_document() 81 | doc.save() 82 | 83 | elif operation == "save_as": 84 | doc = self._get_active_document() 85 | adobe.save_as(doc, file_path) 86 | 87 | elif operation == "reset": 88 | # do nothing and indicate scene was reset to empty 89 | return True 90 | 91 | elif operation == "prepare_new": 92 | # file->new. Not sure how to pop up the actual file->new UI, 93 | # this command will create a document with default properties 94 | adobe.app.documents.add() 95 | 96 | def _get_active_document(self): 97 | """ 98 | Returns the currently open document in Photoshop. 99 | Raises an exeption if no document is active. 100 | """ 101 | doc = self.parent.engine.adobe.get_active_document() 102 | 103 | if not doc: 104 | raise TankError("There is no active document!") 105 | 106 | return doc 107 | -------------------------------------------------------------------------------- /resources/my_tasks_form.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MyTasksForm 4 | 5 | 6 | 7 | 0 8 | 0 9 | 359 10 | 541 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 4 19 | 20 | 21 | 2 22 | 23 | 24 | 6 25 | 26 | 27 | 2 28 | 29 | 30 | 2 31 | 32 | 33 | 34 | 35 | 2 36 | 37 | 38 | 2 39 | 40 | 41 | 1 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | Filter 50 | 51 | 52 | QToolButton::MenuButtonPopup 53 | 54 | 55 | Qt::ToolButtonTextBesideIcon 56 | 57 | 58 | false 59 | 60 | 61 | 62 | 63 | 64 | 65 | Qt::Horizontal 66 | 67 | 68 | 69 | 40 70 | 20 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | + New Task 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 1 88 | 89 | 90 | 1 91 | 92 | 93 | 94 | 95 | 96 | 0 97 | 20 98 | 99 | 100 | 101 | #search_ctrl { 102 | background-color: rgb(255, 128, 0); 103 | } 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | QAbstractItemView::NoEditTriggers 113 | 114 | 115 | false 116 | 117 | 118 | false 119 | 120 | 121 | false 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | SearchWidget 130 | QWidget 131 |
..framework_qtwidgets
132 | 1 133 |
134 |
135 | 136 | 137 |
138 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | import pytest 12 | import subprocess 13 | import time 14 | import os 15 | import sys 16 | 17 | try: 18 | from MA.UI import topwindows 19 | except ImportError: 20 | pytestmark = pytest.mark.skip() 21 | 22 | 23 | # This fixture will launch tk-run-app on first usage 24 | # and will remain valid until the test run ends. 25 | @pytest.fixture(scope="module") 26 | def host_application(tk_test_project, tk_test_entities, commands): 27 | """ 28 | Launch the host application for the Toolkit application. 29 | 30 | TODO: This can probably be refactored, as it is not 31 | likely to change between apps, except for the context. 32 | One way to pass in a context would be to have the repo being 33 | tested to define a fixture named context and this fixture 34 | would consume it. 35 | """ 36 | process = subprocess.Popen( 37 | [ 38 | "python", 39 | "-m", 40 | "tk_toolchain.cmd_line_tools.tk_run_app", 41 | # Allows the test for this application to be invoked from 42 | # another repository, namely the tk-framework-widget repo, 43 | # by specifying that the repo detection should start 44 | # at the specified location. 45 | "--location", 46 | os.path.dirname(__file__), 47 | "--context-entity-type", 48 | tk_test_project["type"], 49 | "--context-entity-id", 50 | str(tk_test_project["id"]), 51 | "--commands", 52 | commands, 53 | "--config", 54 | "tests/fixtures/configWF2ui", 55 | ] 56 | ) 57 | try: 58 | yield 59 | finally: 60 | # We're done. Grab all the output from the process 61 | # and print it so that is there was an error 62 | # we'll know about it. 63 | stdout, stderr = process.communicate() 64 | sys.stdout.write(stdout or "") 65 | sys.stderr.write(stderr or "") 66 | process.poll() 67 | # If returncode is not set, then the process 68 | # was hung and we need to kill it 69 | if process.returncode is None: 70 | process.kill() 71 | else: 72 | assert process.returncode == 0 73 | 74 | 75 | @pytest.fixture(scope="module") 76 | def app_dialog(host_application, window_name): 77 | """ 78 | Retrieve the application dialog and return the AppDialogAppWrapper. 79 | """ 80 | # We're importing sgtk here inside this method as the conftest module will be initialised before sgtk can be found in sys.path. 81 | import sgtk 82 | 83 | before = time.time() 84 | while before + 30 > time.time(): 85 | if sgtk.util.is_windows(): 86 | app_dialog = AppDialogAppWrapper(topwindows[window_name].get()) 87 | else: 88 | app_dialog = AppDialogAppWrapper(topwindows["python"][window_name].get()) 89 | 90 | if app_dialog.exists(): 91 | yield app_dialog 92 | app_dialog.close() 93 | return 94 | else: 95 | raise RuntimeError("Timeout waiting for the app dialog to launch.") 96 | 97 | 98 | class AppDialogAppWrapper(object): 99 | """ 100 | Wrapper around the app dialog. 101 | """ 102 | 103 | def __init__(self, parent): 104 | """ 105 | :param root: 106 | """ 107 | self.root = parent 108 | 109 | def exists(self): 110 | """ 111 | ``True`` if the widget was found, ``False`` otherwise. 112 | """ 113 | return self.root.exists() 114 | 115 | def close(self): 116 | self.root.buttons["Close"].get().mouseClick() 117 | -------------------------------------------------------------------------------- /hooks/scene_operation_tk-photoshop.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | import photoshop 12 | 13 | import sgtk 14 | from sgtk import TankError 15 | 16 | HookClass = sgtk.get_hook_baseclass() 17 | 18 | 19 | class SceneOperation(HookClass): 20 | """ 21 | Hook called to perform an operation with the 22 | current scene 23 | """ 24 | 25 | def execute( 26 | self, 27 | operation, 28 | file_path, 29 | context, 30 | parent_action, 31 | file_version, 32 | read_only, 33 | **kwargs 34 | ): 35 | """ 36 | Main hook entry point 37 | 38 | :param operation: String 39 | Scene operation to perform 40 | 41 | :param file_path: String 42 | File path to use if the operation 43 | requires it (e.g. open) 44 | 45 | :param context: Context 46 | The context the file operation is being 47 | performed in. 48 | 49 | :param parent_action: This is the action that this scene operation is 50 | being executed for. This can be one of: 51 | - open_file 52 | - new_file 53 | - save_file_as 54 | - version_up 55 | 56 | :param file_version: The version/revision of the file to be opened. If this is 'None' 57 | then the latest version should be opened. 58 | 59 | :param read_only: Specifies if the file should be opened read-only or not 60 | 61 | :returns: Depends on operation: 62 | 'current_path' - Return the current scene 63 | file path as a String 64 | 'reset' - True if scene was reset to an empty 65 | state, otherwise False 66 | all others - None 67 | """ 68 | 69 | if operation == "current_path": 70 | # return the current script path 71 | doc = self._get_active_document() 72 | 73 | if doc.fullName is None: 74 | # new file? 75 | path = "" 76 | else: 77 | path = doc.fullName.nativePath 78 | 79 | return path 80 | 81 | elif operation == "open": 82 | # open the specified script 83 | f = photoshop.RemoteObject("flash.filesystem::File", file_path) 84 | photoshop.app.load(f) 85 | 86 | elif operation == "save": 87 | # save the current script: 88 | doc = self._get_active_document() 89 | doc.save() 90 | 91 | elif operation == "save_as": 92 | doc = self._get_active_document() 93 | photoshop.save_as(doc, file_path) 94 | 95 | elif operation == "reset": 96 | # do nothing and indicate scene was reset to empty 97 | return True 98 | 99 | elif operation == "prepare_new": 100 | # file->new. Not sure how to pop up the actual file->new UI, 101 | # this command will create a document with default properties 102 | photoshop.app.documents.add() 103 | 104 | def _get_active_document(self): 105 | """ 106 | Returns the currently open document in Photoshop. 107 | Raises an exeption if no document is active. 108 | """ 109 | doc = photoshop.app.activeDocument 110 | if doc is None: 111 | raise TankError("There is no currently active document!") 112 | return doc 113 | -------------------------------------------------------------------------------- /resources/file_open_form.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | FileOpenForm 4 | 5 | 6 | 7 | 0 8 | 0 9 | 956 10 | 718 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 19 | 20 | 12 21 | 22 | 23 | 24 | 25 | 26 | 80 27 | 30 28 | 29 | 30 | 31 | #history_btns { 32 | background-color: rgb(255, 128, 0); 33 | } 34 | 35 | 36 | 37 | 38 | 39 | 40 | #breadcrumbs { 41 | background-color: rgb(255, 128, 0); 42 | } 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | #browser { 52 | background-color: rgb(255, 128, 0); 53 | } 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | + New File 63 | 64 | 65 | 66 | 67 | 68 | 69 | Qt::Horizontal 70 | 71 | 72 | 73 | 40 74 | 20 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | Cancel 83 | 84 | 85 | 86 | 87 | 88 | 89 | #open_btn { 90 | } 91 | 92 | 93 | Open 94 | 95 | 96 | 97 | 98 | 99 | 100 | Change Context 101 | 102 | 103 | 104 | 105 | 106 | 107 | ... 108 | 109 | 110 | false 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | NavigationWidget 121 | QWidget 122 |
..framework_qtwidgets
123 | 1 124 |
125 | 126 | BreadcrumbWidget 127 | QWidget 128 |
..framework_qtwidgets
129 | 1 130 |
131 | 132 | BrowserForm 133 | QWidget 134 |
..browser_form
135 | 1 136 |
137 |
138 | 139 | 140 | 141 | 142 |
143 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/file_filters.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ 12 | Collection of filters to be used when filtering files in the file views. 13 | """ 14 | 15 | from sgtk.platform.qt import QtCore 16 | 17 | from .user_cache import g_user_cache 18 | 19 | 20 | class FileFilters(QtCore.QObject): 21 | """ 22 | Implementation of the FileFilters class 23 | """ 24 | 25 | # signal emitted whenever something in the filters is changed 26 | changed = QtCore.Signal() 27 | # signal emitted whenever the available users are changed 28 | available_users_changed = QtCore.Signal(object) # list of users 29 | # signal emitted whenever the users changed: 30 | users_changed = QtCore.Signal(object) # list of users 31 | 32 | def __init__(self, parent): 33 | """ 34 | Construction 35 | 36 | :param parent: The parent QObject 37 | """ 38 | QtCore.QObject.__init__(self, parent) 39 | 40 | self._show_all_versions = False 41 | self._filter_reg_exp = QtCore.QRegExp() 42 | self._reset_user_lists() 43 | 44 | # @property 45 | def _get_show_all_versions(self): 46 | return self._show_all_versions 47 | 48 | # @show_all_versions.setter 49 | def _set_show_all_versions(self, show): 50 | if show != self._show_all_versions: 51 | self._show_all_versions = show 52 | self.changed.emit() 53 | 54 | show_all_versions = property(_get_show_all_versions, _set_show_all_versions) 55 | 56 | # @property filter_reg_exp 57 | def _get_filter_reg_exp(self): 58 | return self._filter_reg_exp 59 | 60 | # @filter_reg_exp.setter 61 | def _set_filter_reg_exp(self, value): 62 | if value != self._filter_reg_exp: 63 | self._filter_reg_exp = value 64 | self.changed.emit() 65 | 66 | filter_reg_exp = property(_get_filter_reg_exp, _set_filter_reg_exp) 67 | 68 | @property 69 | def available_users(self): 70 | """ 71 | :returns: List of available user sandboxes. 72 | """ 73 | return self._available_users 74 | 75 | def clear_available_users(self): 76 | """ 77 | Clear the list of available user sandboxes. 78 | """ 79 | self._reset_user_lists() 80 | self.available_users_changed.emit(self._available_users) 81 | 82 | def _reset_user_lists(self): 83 | self._available_users = ( 84 | [g_user_cache.current_user] if g_user_cache.current_user else [] 85 | ) 86 | self._users = [g_user_cache.current_user] if g_user_cache.current_user else [] 87 | 88 | def add_users(self, users): 89 | """ 90 | Adds to the list of available user sandboxes. 91 | 92 | :param users: List of users dictionaries. 93 | """ 94 | nb_users_before = len(self._available_users) 95 | 96 | # merge the two lists, discarding doubles. 97 | new_users_by_id = dict((user["id"], user) for user in users) 98 | available_users_by_id = dict( 99 | (user["id"], user) for user in self._available_users 100 | ) 101 | available_users_by_id.update(new_users_by_id) 102 | self._available_users = list(available_users_by_id.values()) 103 | 104 | # The updated dictionary has grown, so something new was added! 105 | if len(self._available_users) > nb_users_before: 106 | self.available_users_changed.emit(self._available_users) 107 | 108 | # @property 109 | def _get_users(self): 110 | return self._users 111 | 112 | # users.setter 113 | def _set_users(self, users): 114 | current_user_ids = set([u["id"] for u in self._users if u]) 115 | available_user_ids = set([u["id"] for u in self._available_users]) 116 | new_user_ids = set([u["id"] for u in users if u]) & available_user_ids 117 | if new_user_ids != current_user_ids: 118 | self._users = [u for u in users if u and u["id"] in new_user_ids] 119 | self.users_changed.emit(self._users) 120 | self.changed.emit() 121 | 122 | users = property(_get_users, _set_users) 123 | -------------------------------------------------------------------------------- /resources/entity_tree_form.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | EntityTreeForm 4 | 5 | 6 | 7 | 0 8 | 0 9 | 349 10 | 367 11 | 12 | 13 | 14 | Form 15 | 16 | 17 | 18 | 4 19 | 20 | 21 | 2 22 | 23 | 24 | 6 25 | 26 | 27 | 2 28 | 29 | 30 | 2 31 | 32 | 33 | 34 | 35 | 2 36 | 37 | 38 | 2 39 | 40 | 41 | 1 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | My Tasks Only 51 | 52 | 53 | 54 | 55 | 56 | 57 | Qt::Horizontal 58 | 59 | 60 | 61 | 40 62 | 20 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | + New Task 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 1 80 | 81 | 82 | 1 83 | 84 | 85 | 86 | 87 | 88 | 0 89 | 20 90 | 91 | 92 | 93 | Search Entity 94 | 95 | 96 | #search_ctrl { 97 | background-color: rgb(255, 128, 0); 98 | } 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | QTreeView::item { 108 | padding: 2px; 109 | } 110 | 111 | QTreeView::branch:has-children:!has-siblings:closed, 112 | QTreeView::branch:closed:has-children:has-siblings { 113 | border-image: none; 114 | image: url(:/tk-multi-workfiles2/tree_arrow_collapsed.png); 115 | } 116 | 117 | QTreeView::branch:open:has-children:!has-siblings, 118 | QTreeView::branch:open:has-children:has-siblings { 119 | border-image: none; 120 | image: url(:/tk-multi-workfiles2/tree_arrow_expanded.png); 121 | } 122 | 123 | 124 | QAbstractItemView::NoEditTriggers 125 | 126 | 127 | false 128 | 129 | 130 | 131 | 20 132 | 20 133 | 134 | 135 | 136 | false 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | SearchWidget 145 | QWidget 146 |
..framework_qtwidgets
147 | 1 148 |
149 |
150 | 151 | 152 | 153 | 154 |
155 | -------------------------------------------------------------------------------- /hooks/scene_operation_tk-motionbuilder.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | from pyfbsdk import FBApplication 12 | 13 | import sgtk 14 | from sgtk.platform.qt import QtGui 15 | 16 | HookClass = sgtk.get_hook_baseclass() 17 | 18 | 19 | class SceneOperation(HookClass): 20 | """ 21 | Hook called to perform an operation with the 22 | current scene 23 | """ 24 | 25 | def execute( 26 | self, 27 | operation, 28 | file_path, 29 | context, 30 | parent_action, 31 | file_version, 32 | read_only, 33 | **kwargs 34 | ): 35 | """ 36 | Main hook entry point 37 | 38 | :param operation: String 39 | Scene operation to perform 40 | 41 | :param file_path: String 42 | File path to use if the operation 43 | requires it (e.g. open) 44 | 45 | :param context: Context 46 | The context the file operation is being 47 | performed in. 48 | 49 | :param parent_action: This is the action that this scene operation is 50 | being executed for. This can be one of: 51 | - open_file 52 | - new_file 53 | - save_file_as 54 | - version_up 55 | 56 | :param file_version: The version/revision of the file to be opened. If this is 'None' 57 | then the latest version should be opened. 58 | 59 | :param read_only: Specifies if the file should be opened read-only or not 60 | 61 | :returns: Depends on operation: 62 | 'current_path' - Return the current scene 63 | file path as a String 64 | 'reset' - True if scene was reset to an empty 65 | state, otherwise False 66 | all others - None 67 | """ 68 | 69 | fb_app = FBApplication() 70 | 71 | if operation == "current_path": 72 | # return the current scene path 73 | return fb_app.FBXFileName 74 | elif operation == "open": 75 | # do new scene as Maya doesn't like opening 76 | # the scene it currently has open! 77 | fb_app.FileOpen(file_path) 78 | elif operation == "save": 79 | # save the current scene: 80 | # Note - have to pass the current scene name to 81 | # avoid showing the save-as dialog 82 | fb_app.FileSave(fb_app.FBXFileName) 83 | elif operation == "save_as": 84 | fb_app.FileSave(file_path) 85 | elif operation == "reset": 86 | """ 87 | Reset the scene to an empty state 88 | """ 89 | 90 | while True: 91 | # Note, there doesn't appear to be any way to query if 92 | # there are unsaved changes through the MotionBuilder 93 | # Python API. Therefore we just assume there are and 94 | # prompt the user anyway! 95 | res = QtGui.QMessageBox.question( 96 | None, 97 | "Save your scene?", 98 | "Your scene has unsaved changes. Save before proceeding?", 99 | QtGui.QMessageBox.Yes 100 | | QtGui.QMessageBox.No 101 | | QtGui.QMessageBox.Cancel, 102 | ) 103 | 104 | if res == QtGui.QMessageBox.Cancel: 105 | # stop now! 106 | return False 107 | elif res == QtGui.QMessageBox.No: 108 | break 109 | else: 110 | # save the file first 111 | # Note - have to pass the current scene name to 112 | # avoid showing the save-as dialog 113 | if fb_app.FileSave(fb_app.FBXFileName): 114 | break 115 | 116 | # perform file-new 117 | fb_app.FileNew() 118 | return True 119 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/ui/entity_tree_form.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'entity_tree_form.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 5.15.16 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from tank.platform.qt import QtCore 12 | for name, cls in QtCore.__dict__.items(): 13 | if isinstance(cls, type): globals()[name] = cls 14 | 15 | from tank.platform.qt import QtGui 16 | for name, cls in QtGui.__dict__.items(): 17 | if isinstance(cls, type): globals()[name] = cls 18 | 19 | 20 | from ..framework_qtwidgets import SearchWidget 21 | 22 | from . import resources_rc 23 | 24 | class Ui_EntityTreeForm(object): 25 | def setupUi(self, EntityTreeForm): 26 | if not EntityTreeForm.objectName(): 27 | EntityTreeForm.setObjectName(u"EntityTreeForm") 28 | EntityTreeForm.resize(349, 367) 29 | self.verticalLayout = QVBoxLayout(EntityTreeForm) 30 | self.verticalLayout.setSpacing(4) 31 | self.verticalLayout.setObjectName(u"verticalLayout") 32 | self.verticalLayout.setContentsMargins(2, 6, 2, 2) 33 | self.horizontalLayout = QHBoxLayout() 34 | self.horizontalLayout.setObjectName(u"horizontalLayout") 35 | self.horizontalLayout.setContentsMargins(2, -1, 2, 1) 36 | self.task_status_combo = QComboBox(EntityTreeForm) 37 | self.task_status_combo.setObjectName(u"task_status_combo") 38 | 39 | self.horizontalLayout.addWidget(self.task_status_combo) 40 | 41 | self.my_tasks_cb = QCheckBox(EntityTreeForm) 42 | self.my_tasks_cb.setObjectName(u"my_tasks_cb") 43 | 44 | self.horizontalLayout.addWidget(self.my_tasks_cb) 45 | 46 | self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) 47 | 48 | self.horizontalLayout.addItem(self.horizontalSpacer) 49 | 50 | self.new_task_btn = QPushButton(EntityTreeForm) 51 | self.new_task_btn.setObjectName(u"new_task_btn") 52 | 53 | self.horizontalLayout.addWidget(self.new_task_btn) 54 | 55 | self.verticalLayout.addLayout(self.horizontalLayout) 56 | 57 | self.horizontalLayout_2 = QHBoxLayout() 58 | self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") 59 | self.horizontalLayout_2.setContentsMargins(1, -1, 1, -1) 60 | self.search_ctrl = SearchWidget(EntityTreeForm) 61 | self.search_ctrl.setObjectName(u"search_ctrl") 62 | self.search_ctrl.setMinimumSize(QSize(0, 20)) 63 | self.search_ctrl.setStyleSheet(u"#search_ctrl {\n" 64 | "background-color: rgb(255, 128, 0);\n" 65 | "}") 66 | 67 | self.horizontalLayout_2.addWidget(self.search_ctrl) 68 | 69 | self.verticalLayout.addLayout(self.horizontalLayout_2) 70 | 71 | self.entity_tree = QTreeView(EntityTreeForm) 72 | self.entity_tree.setObjectName(u"entity_tree") 73 | self.entity_tree.setStyleSheet(u"QTreeView::item {\n" 74 | "padding: 2px;\n" 75 | "}\n" 76 | "\n" 77 | "QTreeView::branch:has-children:!has-siblings:closed,\n" 78 | "QTreeView::branch:closed:has-children:has-siblings {\n" 79 | " border-image: none;\n" 80 | " image: url(:/tk-multi-workfiles2/tree_arrow_collapsed.png);\n" 81 | "}\n" 82 | "\n" 83 | "QTreeView::branch:open:has-children:!has-siblings,\n" 84 | "QTreeView::branch:open:has-children:has-siblings {\n" 85 | " border-image: none;\n" 86 | " image: url(:/tk-multi-workfiles2/tree_arrow_expanded.png);\n" 87 | "}") 88 | self.entity_tree.setEditTriggers(QAbstractItemView.NoEditTriggers) 89 | self.entity_tree.setProperty("showDropIndicator", False) 90 | self.entity_tree.setIconSize(QSize(20, 20)) 91 | self.entity_tree.header().setVisible(False) 92 | 93 | self.verticalLayout.addWidget(self.entity_tree) 94 | 95 | self.verticalLayout.setStretch(2, 1) 96 | 97 | self.retranslateUi(EntityTreeForm) 98 | 99 | QMetaObject.connectSlotsByName(EntityTreeForm) 100 | # setupUi 101 | 102 | def retranslateUi(self, EntityTreeForm): 103 | EntityTreeForm.setWindowTitle(QCoreApplication.translate("EntityTreeForm", u"Form", None)) 104 | self.my_tasks_cb.setText(QCoreApplication.translate("EntityTreeForm", u"My Tasks Only", None)) 105 | self.new_task_btn.setText(QCoreApplication.translate("EntityTreeForm", u"+ New Task", None)) 106 | #if QT_CONFIG(accessibility) 107 | self.search_ctrl.setAccessibleName(QCoreApplication.translate("EntityTreeForm", u"Search Entity", None)) 108 | #endif // QT_CONFIG(accessibility) 109 | # retranslateUi 110 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/ui/file_open_form.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'file_open_form.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 5.15.2 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from tank.platform.qt import QtCore 12 | for name, cls in QtCore.__dict__.items(): 13 | if isinstance(cls, type): globals()[name] = cls 14 | 15 | from tank.platform.qt import QtGui 16 | for name, cls in QtGui.__dict__.items(): 17 | if isinstance(cls, type): globals()[name] = cls 18 | 19 | 20 | from ..framework_qtwidgets import NavigationWidget 21 | from ..framework_qtwidgets import BreadcrumbWidget 22 | from ..browser_form import BrowserForm 23 | 24 | from . import resources_rc 25 | 26 | class Ui_FileOpenForm(object): 27 | def setupUi(self, FileOpenForm): 28 | if not FileOpenForm.objectName(): 29 | FileOpenForm.setObjectName(u"FileOpenForm") 30 | FileOpenForm.resize(956, 718) 31 | self.verticalLayout = QVBoxLayout(FileOpenForm) 32 | self.verticalLayout.setObjectName(u"verticalLayout") 33 | self.horizontalLayout_3 = QHBoxLayout() 34 | self.horizontalLayout_3.setSpacing(12) 35 | self.horizontalLayout_3.setObjectName(u"horizontalLayout_3") 36 | self.nav = NavigationWidget(FileOpenForm) 37 | self.nav.setObjectName(u"nav") 38 | self.nav.setMinimumSize(QSize(80, 30)) 39 | self.nav.setStyleSheet(u"#history_btns {\n" 40 | "background-color: rgb(255, 128, 0);\n" 41 | "}") 42 | 43 | self.horizontalLayout_3.addWidget(self.nav) 44 | 45 | self.breadcrumbs = BreadcrumbWidget(FileOpenForm) 46 | self.breadcrumbs.setObjectName(u"breadcrumbs") 47 | self.breadcrumbs.setStyleSheet(u"#breadcrumbs {\n" 48 | "background-color: rgb(255, 128, 0);\n" 49 | "}") 50 | 51 | self.horizontalLayout_3.addWidget(self.breadcrumbs) 52 | 53 | self.horizontalLayout_3.setStretch(1, 1) 54 | 55 | self.verticalLayout.addLayout(self.horizontalLayout_3) 56 | 57 | self.browser = BrowserForm(FileOpenForm) 58 | self.browser.setObjectName(u"browser") 59 | self.browser.setStyleSheet(u"#browser {\n" 60 | "background-color: rgb(255, 128, 0);\n" 61 | "}") 62 | 63 | self.verticalLayout.addWidget(self.browser) 64 | 65 | self.horizontalLayout = QHBoxLayout() 66 | self.horizontalLayout.setObjectName(u"horizontalLayout") 67 | self.new_file_btn = QPushButton(FileOpenForm) 68 | self.new_file_btn.setObjectName(u"new_file_btn") 69 | 70 | self.horizontalLayout.addWidget(self.new_file_btn) 71 | 72 | self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) 73 | 74 | self.horizontalLayout.addItem(self.horizontalSpacer) 75 | 76 | self.cancel_btn = QPushButton(FileOpenForm) 77 | self.cancel_btn.setObjectName(u"cancel_btn") 78 | 79 | self.horizontalLayout.addWidget(self.cancel_btn) 80 | 81 | self.open_btn = QPushButton(FileOpenForm) 82 | self.open_btn.setObjectName(u"open_btn") 83 | self.open_btn.setStyleSheet(u"#open_btn {\n" 84 | "}") 85 | 86 | self.horizontalLayout.addWidget(self.open_btn) 87 | 88 | self.change_ctx_btn = QPushButton(FileOpenForm) 89 | self.change_ctx_btn.setObjectName(u"change_ctx_btn") 90 | 91 | self.horizontalLayout.addWidget(self.change_ctx_btn) 92 | 93 | self.open_options_btn = QPushButton(FileOpenForm) 94 | self.open_options_btn.setObjectName(u"open_options_btn") 95 | self.open_options_btn.setFlat(False) 96 | 97 | self.horizontalLayout.addWidget(self.open_options_btn) 98 | 99 | self.verticalLayout.addLayout(self.horizontalLayout) 100 | 101 | self.verticalLayout.setStretch(1, 1) 102 | 103 | self.retranslateUi(FileOpenForm) 104 | 105 | QMetaObject.connectSlotsByName(FileOpenForm) 106 | # setupUi 107 | 108 | def retranslateUi(self, FileOpenForm): 109 | FileOpenForm.setWindowTitle(QCoreApplication.translate("FileOpenForm", u"Form", None)) 110 | self.new_file_btn.setText(QCoreApplication.translate("FileOpenForm", u"+ New File", None)) 111 | self.cancel_btn.setText(QCoreApplication.translate("FileOpenForm", u"Cancel", None)) 112 | self.open_btn.setText(QCoreApplication.translate("FileOpenForm", u"Open", None)) 113 | self.change_ctx_btn.setText(QCoreApplication.translate("FileOpenForm", u"Change Context", None)) 114 | self.open_options_btn.setText(QCoreApplication.translate("FileOpenForm", u"...", None)) 115 | # retranslateUi 116 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/file_list/user_filter_button.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ 12 | QPushButton containing a menu that allows selection of users from a list of available users. The button 13 | updates it's icon depending on the current selection in the menu. 14 | """ 15 | 16 | from sgtk.platform.qt import QtCore, QtGui 17 | 18 | from .user_filter_menu import UserFilterMenu 19 | 20 | 21 | class UserFilterButton(QtGui.QPushButton): 22 | """ 23 | Button that when pressed will show the list of user sandboxes available. 24 | """ 25 | 26 | users_selected = QtCore.Signal(object) # list of users 27 | 28 | _USER_STYLE_NONE = "none" 29 | _USER_STYLE_CURRENT = "current" 30 | _USER_STYLE_OTHER = "other" 31 | _USER_STYLE_ALL = "all" 32 | 33 | def __init__(self, parent): 34 | """ 35 | Constructor. 36 | 37 | :param parent: Parent widget. 38 | """ 39 | QtGui.QPushButton.__init__(self, parent) 40 | 41 | users_menu = UserFilterMenu(self) 42 | users_menu.users_selected.connect(self._on_menu_users_selected) 43 | self.setMenu(users_menu) 44 | self._update() 45 | 46 | # @property 47 | def _get_selected_users(self): 48 | """ 49 | Retrieves the list of selected users in the user filter menu. 50 | 51 | :returns: List of selected users entities. 52 | """ 53 | return self.menu().selected_users 54 | 55 | # selected_users.setter 56 | def _set_selected_users(self, users): 57 | """ 58 | Sets the lists of users selected in the user filter menu. 59 | 60 | :param users: List of user entities. 61 | """ 62 | self.menu().selected_users = users 63 | self._update() 64 | 65 | selected_users = property(_get_selected_users, _set_selected_users) 66 | 67 | # @property 68 | def _get_available_users(self): 69 | """ 70 | Retrieves the list of users available for selection in the user filter menu. 71 | 72 | :returns: List of available users entities. 73 | """ 74 | return self.menu().available_users 75 | 76 | # available_users.setter 77 | def _set_available_users(self, users): 78 | """ 79 | Sets the list of users available for selection in the user filter menu. 80 | 81 | :users users: List of user entities. 82 | """ 83 | self.menu().available_users = users 84 | self._update() 85 | 86 | available_users = property(_get_available_users, _set_available_users) 87 | 88 | def _on_menu_users_selected(self, users): 89 | """ 90 | Called whenever the selection changes in the user filter menu. 91 | 92 | :params users: List of users that are selected. 93 | """ 94 | self.users_selected.emit(users) 95 | self._update() 96 | 97 | def showEvent(self, event): 98 | """ 99 | Ensures the widget look is updated when it is enabled or disabled. 100 | 101 | :param event: QtCore.QShowEvent object. 102 | """ 103 | self._update() 104 | return QtGui.QPushButton.showEvent(self, event) 105 | 106 | def changeEvent(self, event): 107 | """ 108 | Ensures the widget look is updated when it is enabled or disabled. 109 | 110 | :param event: QtCore.QEvent object. 111 | """ 112 | if event.type() == QtCore.QEvent.EnabledChange: 113 | self._update() 114 | return QtGui.QPushButton.changeEvent(self, event) 115 | 116 | def _update(self): 117 | """ 118 | Updates the status of the button. 119 | """ 120 | # figure out the style to use: 121 | user_style = self._USER_STYLE_NONE 122 | if self.menu().isEnabled(): 123 | if self.menu().current_user_selected: 124 | if self.menu().other_users_selected: 125 | user_style = self._USER_STYLE_ALL 126 | else: 127 | user_style = self._USER_STYLE_CURRENT 128 | elif self.menu().other_users_selected: 129 | user_style = self._USER_STYLE_OTHER 130 | 131 | # set the property on the filter btn: 132 | self.setProperty("user_style", user_style) 133 | 134 | # unpolish/repolish to update the style sheet: 135 | self.style().unpolish(self) 136 | self.ensurePolished() 137 | self.repaint() 138 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/actions/open_workfile_actions.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ """ 12 | import sgtk 13 | from sgtk.platform.qt import QtCore, QtGui 14 | from sgtk import TankError 15 | 16 | from .open_file_action import ( 17 | OpenFileAction, 18 | CopyAndOpenInCurrentWorkAreaAction, 19 | ContinueFromFileAction, 20 | ) 21 | 22 | from ..user_cache import g_user_cache 23 | 24 | 25 | class OpenWorkfileAction(OpenFileAction): 26 | """ """ 27 | 28 | def __init__(self, file, file_versions, environment): 29 | """ """ 30 | all_versions = [v for v, f in file_versions.items()] 31 | max_version = max(all_versions) if all_versions else 0 32 | 33 | sandbox_user = None 34 | if ( 35 | environment 36 | and environment.contains_user_sandboxes 37 | and environment.context 38 | and environment.context.user 39 | and g_user_cache.current_user 40 | and environment.context.user["id"] != g_user_cache.current_user["id"] 41 | ): 42 | sandbox_user = environment.context.user.get("name", "Unknown").split(" ")[0] 43 | 44 | label = "" 45 | if file.version == max_version: 46 | label = "Open" 47 | else: 48 | label = "Open v%03d" % file.version 49 | if not file.editable: 50 | label = "%s (Read-only)" % label 51 | if sandbox_user is not None: 52 | label = "%s from %s's Sandbox" % (label, sandbox_user) 53 | 54 | OpenFileAction.__init__(self, label, file, file_versions, environment) 55 | 56 | def execute(self, parent_ui): 57 | """ 58 | Handles opening a work file - this checks to see if the file 59 | is in another users sandbox before opening 60 | """ 61 | if not self.file or not self.file.is_local: 62 | return False 63 | 64 | return self._do_copy_and_open( 65 | src_path=None, 66 | dst_path=self.file.path, 67 | version=self.file.version, 68 | read_only=self.file.editable, 69 | new_ctx=self.environment.context, 70 | parent_ui=parent_ui, 71 | check_refs=True, 72 | ) 73 | 74 | 75 | class ContinueFromWorkFileAction(ContinueFromFileAction): 76 | """ """ 77 | 78 | def __init__(self, file, file_versions, environment): 79 | """ """ 80 | label = "" 81 | if ( 82 | environment 83 | and environment.contains_user_sandboxes 84 | and environment.context 85 | and environment.context.user 86 | and g_user_cache.current_user 87 | and environment.context.user["id"] != g_user_cache.current_user["id"] 88 | ): 89 | sandbox_user = environment.context.user.get("name", "Unknown").split(" ")[0] 90 | label = "Continue Working from %s's File" % sandbox_user 91 | else: 92 | label = "Continue Working" 93 | 94 | ContinueFromFileAction.__init__(self, label, file, file_versions, environment) 95 | 96 | def execute(self, parent_ui): 97 | """ """ 98 | if not self.file.is_local or not self.environment.work_template: 99 | return False 100 | 101 | # source path is the file path: 102 | src_path = self.file.path 103 | 104 | return self._continue_from(src_path, self.environment.work_template, parent_ui) 105 | 106 | 107 | class CopyAndOpenFileInCurrentWorkAreaAction(CopyAndOpenInCurrentWorkAreaAction): 108 | """ 109 | Action that copies a file to the current work area as the next available version 110 | and opens it from there 111 | """ 112 | 113 | def __init__(self, file, file_versions, environment): 114 | """ """ 115 | CopyAndOpenInCurrentWorkAreaAction.__init__( 116 | self, "Open in Current Work Area...", file, file_versions, environment 117 | ) 118 | 119 | def execute(self, parent_ui): 120 | """ """ 121 | if ( 122 | not self.file 123 | or not self.file.is_local 124 | or not self.environment.work_template 125 | ): 126 | return False 127 | 128 | return self._open_in_current_work_area( 129 | self.file.path, 130 | self.environment.work_template, 131 | self.file, 132 | self.environment, 133 | parent_ui, 134 | ) 135 | -------------------------------------------------------------------------------- /tests/test_change_context_gui.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | import pytest 12 | 13 | try: 14 | from MA.UI import topwindows 15 | except ImportError: 16 | pytestmark = pytest.mark.skip() 17 | 18 | 19 | @pytest.fixture(scope="module") 20 | def commands(): 21 | """ 22 | Return the command to launch Workfiles2 in different mode. 23 | This fixture is used by the host_application fixture in conftest.py 24 | """ 25 | return "change_context" 26 | 27 | 28 | @pytest.fixture(scope="module") 29 | def window_name(): 30 | """ 31 | Return the window app name. 32 | This fixture is used by the app_dialog fixture in conftest.py 33 | """ 34 | return "Flow Production Tracking: Change Context" 35 | 36 | 37 | def test_ui_validation(app_dialog, tk_test_project): 38 | """ 39 | Basic UI validation to make sure all buttons, tabs and fields are available 40 | """ 41 | # Make Sure the Change Context dialog is showing up in the right context 42 | assert app_dialog.root.captions[ 43 | "Change Context" 44 | ].exists(), "Not the Change Context dialog" 45 | assert app_dialog.root.captions[ 46 | "*Project " + tk_test_project["name"] 47 | ].exists(), "Not the right context" 48 | 49 | # Make sure the breadcrumb UI is fine. 50 | assert app_dialog.root.buttons[ 51 | "nav_home_btn" 52 | ].exists(), "Nav home button is missing" 53 | assert app_dialog.root.buttons[ 54 | "nav_prev_btn" 55 | ].exists(), "Nav previous button is missing" 56 | assert app_dialog.root.buttons[ 57 | "nav_next_btn" 58 | ].exists(), "Nav next button is missing" 59 | assert app_dialog.root.captions["My Tasks"].exists(), "Not the right breadcrumb" 60 | 61 | # Make sure the all tabs are showing up. 62 | assert app_dialog.root.tabs["My Tasks"].exists(), "My Tasks tab is missing" 63 | assert app_dialog.root.tabs["Assets"].exists(), "Assets tab is missing" 64 | assert app_dialog.root.tabs["Shots"].exists(), "Shots tab is missing" 65 | 66 | # Make sure all buttons are showing up 67 | assert app_dialog.root.buttons[ 68 | "+ New Task" 69 | ].exists(), "+ New Task button is missing" 70 | assert app_dialog.root.buttons["Cancel"].exists(), "Cancel button is missing" 71 | assert app_dialog.root.buttons["Change Context"].exists(), "Open button is missing" 72 | 73 | # Make sure all test fields are showing up 74 | assert app_dialog.root[ 75 | "Search Entity" 76 | ].exists(), "Search My Tasks text field is missing" 77 | # Create a new task and select it 78 | app_dialog.root.tabs["Assets"].mouseClick() 79 | app_dialog.root.outlineitems["Character"].waitExist(timeout=30) 80 | app_dialog.root.outlineitems["Character"].mouseDoubleClick() 81 | app_dialog.root.outlineitems["AssetAutomation"].waitExist(timeout=30) 82 | app_dialog.root.outlineitems["AssetAutomation"].mouseDoubleClick() 83 | app_dialog.root.buttons["+ New Task"].mouseClick() 84 | app_dialog.root.dialogs["Flow Production Tracking: Create New Task"].waitExist( 85 | timeout=30 86 | ) 87 | app_dialog.root.dialogs["Flow Production Tracking: Create New Task"].textfields[ 88 | "Task Name" 89 | ].pasteIn("New Texture Task") 90 | app_dialog.root.dialogs["Flow Production Tracking: Create New Task"].dropdowns[ 91 | "Pipeline Step" 92 | ].mouseClick() 93 | topwindows.listitems["Texture"].waitExist(timeout=30) 94 | topwindows.listitems["Texture"].mouseClick() 95 | app_dialog.root.dialogs["Flow Production Tracking: Create New Task"].buttons[ 96 | "Create" 97 | ].mouseClick() 98 | app_dialog.root.outlineitems["Texture"].waitExist(timeout=30) 99 | # Enable My Tasks Only and make sure Model task is not showing up anymore 100 | app_dialog.root.checkboxes["My Tasks Only"].mouseClick() 101 | assert app_dialog.root.outlineitems[ 102 | "Texture" 103 | ].exists(), "New Texture task should be visible" 104 | 105 | # Go back to My Tasks and make sure New Texture Task is showing up and select it 106 | app_dialog.root.tabs["My Tasks"].mouseClick() 107 | app_dialog.root.outlineitems["New Texture Task"].waitExist(timeout=30) 108 | app_dialog.root.outlineitems["New Texture Task"].mouseClick() 109 | assert ( 110 | app_dialog.root.outlineitems["New Texture Task"].selected 111 | or app_dialog.root.outlineitems["New Texture Task"].focused is True 112 | ) 113 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/actions/custom_file_action.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ """ 12 | 13 | import sgtk 14 | 15 | from .file_action import FileAction 16 | 17 | 18 | class CustomFileAction(FileAction): 19 | @staticmethod 20 | def _prepare_file_data_for_hook(file_versions): 21 | """ """ 22 | work_file_versions = [] 23 | publish_versions = [] 24 | for file in file_versions: 25 | if file.is_local: 26 | work_file = {} 27 | work_file["name"] = file.name 28 | work_file["path"] = file.path 29 | work_file["version"] = file.version 30 | work_file["modified_at"] = file.modified_at 31 | work_file["modified_by"] = file.modified_by 32 | work_file["read_only"] = not file.editable 33 | work_file["type"] = "work" 34 | work_file_versions.append(work_file) 35 | if file.is_published: 36 | publish = {} 37 | publish["name"] = file.name 38 | publish["path"] = file.publish_path 39 | publish["version"] = file.version 40 | publish["published_at"] = file.published_at 41 | publish["published_by"] = file.published_by 42 | publish["type"] = "publish" 43 | publish_versions.append(publish) 44 | 45 | return work_file_versions, publish_versions 46 | 47 | @staticmethod 48 | def get_action_details( 49 | file, file_versions, environment, workfiles_visible, publishes_visible 50 | ): 51 | """ """ 52 | app = sgtk.platform.current_bundle() 53 | 54 | # build hook-friendly data: 55 | work_file, publish = CustomFileAction._prepare_file_data_for_hook([file]) 56 | hook_file = None 57 | if workfiles_visible: 58 | hook_file = work_file[0] if work_file else None 59 | if not hook_file and publishes_visible: 60 | hook_file = publish[0] if publish else None 61 | work_versions, publish_versions = CustomFileAction._prepare_file_data_for_hook( 62 | list(file_versions.values()) 63 | ) 64 | 65 | # execute hook method to get actions: 66 | action_info = [] 67 | try: 68 | action_info = app.execute_hook_method( 69 | "custom_actions_hook", 70 | "generate_actions", 71 | file=hook_file, 72 | work_versions=work_versions, 73 | publish_versions=publish_versions, 74 | context=environment.context, 75 | ) 76 | except: 77 | app.log_exception("Failed to retrieve custom actions from Hook!") 78 | 79 | return action_info 80 | 81 | def __init__( 82 | self, 83 | name, 84 | label, 85 | file, 86 | file_versions, 87 | environment, 88 | workfiles_visible, 89 | publishes_visible, 90 | ): 91 | """ 92 | Construction 93 | """ 94 | FileAction.__init__(self, label, file, file_versions, environment) 95 | self._name = name 96 | self._workfiles_visible = workfiles_visible 97 | self._publishes_visible = publishes_visible 98 | 99 | def execute(self, parent_ui): 100 | """ """ 101 | # execute hook to perform the action 102 | app = sgtk.platform.current_bundle() 103 | 104 | # build hook-friendly data: 105 | work_file, publish = CustomFileAction._prepare_file_data_for_hook([self.file]) 106 | hook_file = None 107 | if self._workfiles_visible: 108 | hook_file = work_file[0] if work_file else None 109 | if not hook_file and self._publishes_visible: 110 | hook_file = publish[0] if publish else None 111 | work_versions, publish_versions = CustomFileAction._prepare_file_data_for_hook( 112 | list(self.file_versions.values()) 113 | ) 114 | 115 | # execute hook method to execute action: 116 | result = False 117 | try: 118 | result = app.execute_hook_method( 119 | "custom_actions_hook", 120 | "execute_action", 121 | action=self._name, 122 | file=hook_file, 123 | work_versions=work_versions, 124 | publish_versions=publish_versions, 125 | context=self.environment.context, 126 | ) 127 | except: 128 | app.log_exception("Failed to execute custom action!") 129 | 130 | return result 131 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/entity_proxy_model.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | import sgtk 12 | from sgtk.platform.qt import QtCore, QtGui 13 | 14 | from .framework_qtwidgets import HierarchicalFilteringProxyModel 15 | 16 | from .util import get_model_str 17 | 18 | 19 | class EntityProxyModel(HierarchicalFilteringProxyModel): 20 | """ """ 21 | 22 | def __init__(self, parent, compare_sg_fields=None): 23 | """ """ 24 | HierarchicalFilteringProxyModel.__init__(self, parent) 25 | self._compare_fields = compare_sg_fields 26 | 27 | def setFilterFixedString(self, pattern): 28 | """ 29 | Overriden base class method to set the filter fixed string 30 | """ 31 | # ensure model is fully loaded before we attempt any searching 32 | self.sourceModel().ensure_data_is_loaded() 33 | 34 | # call base class 35 | return super().setFilterFixedString(pattern) 36 | 37 | def setFilterRegExp(self, reg_exp): 38 | """ 39 | Overriden base class method to set the filter regular expression 40 | """ 41 | # ensure model is fully loaded before we attempt any searching 42 | self.sourceModel().ensure_data_is_loaded() 43 | 44 | # call base class 45 | return super().setFilterRegExp(reg_exp) 46 | 47 | def ensure_data_is_loaded(self, index=None): 48 | """ 49 | Recursively processes the model and ensures that all data 50 | has been loaded into the shotgun model contained by the proxy model. 51 | 52 | :param index: Model index for which to recursively load data. 53 | If set to None, the entire tree will be loaded. 54 | :type index: :class:`~PySide.QtCore.QModelIndex` 55 | """ 56 | # convert proxy indices to internal model indices 57 | source_index = self.mapToSource(index) if index else None 58 | 59 | return self.sourceModel().ensure_data_is_loaded(source_index) 60 | 61 | def _is_row_accepted(self, src_row, src_parent_idx, parent_accepted): 62 | """ 63 | Overriden from base class - determines if the specified row should be accepted or not by 64 | the filter. 65 | 66 | :param src_row: The row in the source model to filter 67 | :param src_parent_idx: The parent QModelIndex instance to filter 68 | :param parent_accepted: True if a parent item has been accepted by the filter 69 | :returns: True if this index should be accepted, otherwise False 70 | """ 71 | # if the parent is accepted then this node is accepted by default: 72 | if parent_accepted: 73 | return True 74 | 75 | reg_exp = self.filterRegExp() 76 | if not reg_exp or reg_exp.isEmpty(): 77 | # early out 78 | return True 79 | 80 | src_idx = self.sourceModel().index(src_row, 0, src_parent_idx) 81 | if not src_idx.isValid(): 82 | return False 83 | 84 | # test to see if the item 'text' matches: 85 | if reg_exp.indexIn(get_model_str(src_idx)) != -1: 86 | # found a match so early out! 87 | return True 88 | 89 | if self._compare_fields: 90 | # see if we have sg data: 91 | item = src_idx.model().itemFromIndex(src_idx) 92 | sg_data = item.get_sg_data() 93 | if sg_data: 94 | return self._sg_data_matches_r(sg_data, self._compare_fields, reg_exp) 95 | 96 | # default is to not match! 97 | return False 98 | 99 | def _sg_data_matches_r(self, sg_data, compare_fields, reg_exp): 100 | """ """ 101 | if isinstance(compare_fields, list): 102 | # e.g. ["one", "two", {"three":"four", "five":["six", "seven"]}] 103 | for cf in compare_fields: 104 | if isinstance(cf, dict): 105 | # e.g. {"three":"four", "five":["six", "seven"]} 106 | for key, value in cf.items(): 107 | data = sg_data.get(key) 108 | if data: 109 | if self._sg_data_matches_r(data, value, reg_exp): 110 | return True 111 | else: 112 | # e.g. "one" 113 | if self._sg_data_matches_r(sg_data, cf, reg_exp): 114 | return True 115 | else: 116 | # e.g. "one" 117 | val = sg_data.get(compare_fields) 118 | if val != None and reg_exp.indexIn(str(val)) != -1: 119 | return True 120 | 121 | return False 122 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/my_tasks/my_tasks_form.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | """ 12 | Implementation of the my tasks list widget consisting of a list view displaying the contents 13 | of a Shotgun data model of my tasks, a text search and a filter control. 14 | """ 15 | 16 | from ..util import monitor_qobject_lifetime 17 | from ..entity_tree.entity_tree_form import EntityTreeForm 18 | from ..framework_qtwidgets import ViewItemDelegate, sg_qwidgets 19 | 20 | from sgtk.platform.qt import QtCore, QtGui 21 | 22 | 23 | class MyTasksForm(EntityTreeForm): 24 | """ 25 | My Tasks widget class 26 | """ 27 | 28 | # emitted when an entity is double clicked 29 | task_double_clicked = QtCore.Signal(object) 30 | 31 | def __init__(self, tasks_model, allow_task_creation, parent): 32 | """ 33 | Construction 34 | 35 | :param model: The Shotgun Model this widget should connect to 36 | :param parent: The parent QWidget for this control 37 | """ 38 | EntityTreeForm.__init__( 39 | self, 40 | tasks_model, 41 | "My Tasks", 42 | allow_task_creation, 43 | tasks_model.extra_display_fields, 44 | parent, 45 | ) 46 | 47 | # There is no need for the my tasks toggle. 48 | self._ui.my_tasks_cb.hide() 49 | 50 | # Task status filter 51 | self._ui.task_status_combo.show() 52 | 53 | # Sets an item delete to show a list of tiles for tasks instead of nodes in a tree. 54 | # Make sure we keep a reference to the delegate otherwise things may crash later on 55 | self._item_delegate = self._create_delegate(tasks_model, self._ui.entity_tree) 56 | 57 | monitor_qobject_lifetime(self._item_delegate) 58 | self._ui.entity_tree.setItemDelegate(self._item_delegate) 59 | 60 | self._ui.entity_tree.doubleClicked.connect(self._on_double_clicked) 61 | 62 | self._sort_button_setup() 63 | 64 | def shut_down(self): 65 | """ 66 | Clean up as much as we can to help the gc once the widget is finished with. 67 | """ 68 | signals_blocked = self.blockSignals(True) 69 | try: 70 | EntityTreeForm.shut_down(self) 71 | # detach and clean up the item delegate: 72 | self._ui.entity_tree.setItemDelegate(None) 73 | if self._item_delegate: 74 | self._item_delegate.setParent(None) 75 | self._item_delegate.deleteLater() 76 | self._item_delegate = None 77 | finally: 78 | self.blockSignals(signals_blocked) 79 | 80 | def _on_double_clicked(self, idx): 81 | """ 82 | Emits the entity that was double clicked. 83 | """ 84 | entity_details = self._get_entity_details(idx) 85 | self.task_double_clicked.emit(entity_details) 86 | 87 | def _create_delegate(self, model, view): 88 | """Create the delegate for the tree view.""" 89 | 90 | delegate = ViewItemDelegate(view) 91 | 92 | delegate.thumbnail_role = model.VIEW_ITEM_THUMBNAIL_ROLE 93 | delegate.header_role = model.VIEW_ITEM_HEADER_ROLE 94 | delegate.subtitle_role = model.VIEW_ITEM_SUBTITLE_ROLE 95 | delegate.text_role = model.VIEW_ITEM_TEXT_ROLE 96 | delegate.icon_role = model.VIEW_ITEM_ICON_ROLE 97 | delegate.expand_role = model.VIEW_ITEM_EXPAND_ROLE 98 | delegate.width_role = model.VIEW_ITEM_WIDTH_ROLE 99 | delegate.height_role = model.VIEW_ITEM_HEIGHT_ROLE 100 | delegate.loading_role = model.VIEW_ITEM_LOADING_ROLE 101 | delegate.separator_role = model.VIEW_ITEM_SEPARATOR_ROLE 102 | 103 | delegate.text_rect_valign = ViewItemDelegate.CENTER 104 | delegate.override_item_tooltip = True 105 | delegate.thumbnail_padding = 6 106 | 107 | delegate.item_height = 64 108 | delegate.thumbnail_padding = ViewItemDelegate.Padding(7, 0, 7, 7) 109 | delegate.thumbnail_uniform = True 110 | 111 | view.setMouseTracking(True) 112 | view.setRootIsDecorated(False) 113 | 114 | return delegate 115 | 116 | def _sort_button_setup(self): 117 | self.sort_menu_button = sg_qwidgets.SGQToolButton() 118 | self.sort_menu_button.setText("Sort") 119 | self.sort_menu_button.setObjectName("sort_menu_button") 120 | self.sort_menu_button.setStyleSheet("border :None") 121 | self.sort_menu_button.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) 122 | self.sort_menu_button.setMinimumHeight(24) 123 | self._ui.horizontalLayout_2.addWidget(self.sort_menu_button) 124 | -------------------------------------------------------------------------------- /hooks/scene_operation_tk-maya.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | import maya.cmds as cmds 12 | 13 | import sgtk 14 | from sgtk.platform.qt import QtGui 15 | 16 | HookClass = sgtk.get_hook_baseclass() 17 | 18 | 19 | class SceneOperation(HookClass): 20 | """ 21 | Hook called to perform an operation with the 22 | current scene 23 | """ 24 | 25 | def execute( 26 | self, 27 | operation, 28 | file_path, 29 | context, 30 | parent_action, 31 | file_version, 32 | read_only, 33 | **kwargs 34 | ): 35 | """ 36 | Main hook entry point 37 | 38 | :param operation: String 39 | Scene operation to perform 40 | 41 | :param file_path: String 42 | File path to use if the operation 43 | requires it (e.g. open) 44 | 45 | :param context: Context 46 | The context the file operation is being 47 | performed in. 48 | 49 | :param parent_action: This is the action that this scene operation is 50 | being executed for. This can be one of: 51 | - open_file 52 | - new_file 53 | - save_file_as 54 | - version_up 55 | 56 | :param file_version: The version/revision of the file to be opened. If this is 'None' 57 | then the latest version should be opened. 58 | 59 | :param read_only: Specifies if the file should be opened read-only or not 60 | 61 | :returns: Depends on operation: 62 | 'current_path' - Return the current scene 63 | file path as a String 64 | 'reset' - True if scene was reset to an empty 65 | state, otherwise False 66 | all others - None 67 | """ 68 | 69 | if operation == "current_path": 70 | # return the current scene path 71 | return cmds.file(query=True, sceneName=True) 72 | elif operation == "open": 73 | # do new scene as Maya doesn't like opening 74 | # the scene it currently has open! 75 | cmds.file(new=True, force=True) 76 | cmds.file(file_path, open=True, force=True) 77 | elif operation == "save": 78 | # save the current scene: 79 | cmds.file(save=True) 80 | elif operation == "save_as": 81 | # first rename the scene as file_path: 82 | cmds.file(rename=file_path) 83 | 84 | # Maya can choose the wrong file type so 85 | # we should set it here explicitely based 86 | # on the extension 87 | maya_file_type = None 88 | if file_path.lower().endswith(".ma"): 89 | maya_file_type = "mayaAscii" 90 | elif file_path.lower().endswith(".mb"): 91 | maya_file_type = "mayaBinary" 92 | 93 | # save the scene: 94 | if maya_file_type: 95 | cmds.file(save=True, force=True, type=maya_file_type) 96 | else: 97 | cmds.file(save=True, force=True) 98 | 99 | elif operation == "reset": 100 | """ 101 | Reset the scene to an empty state 102 | """ 103 | while cmds.file(query=True, modified=True): 104 | # changes have been made to the scene 105 | res = QtGui.QMessageBox.question( 106 | None, 107 | "Save your scene?", 108 | "Your scene has unsaved changes. Save before proceeding?", 109 | QtGui.QMessageBox.Yes 110 | | QtGui.QMessageBox.No 111 | | QtGui.QMessageBox.Cancel, 112 | ) 113 | 114 | if res == QtGui.QMessageBox.Cancel: 115 | return False 116 | elif res == QtGui.QMessageBox.No: 117 | break 118 | else: 119 | scene_name = cmds.file(query=True, sn=True) 120 | if not scene_name: 121 | cmds.SaveSceneAs() 122 | else: 123 | cmds.file(save=True) 124 | 125 | # do new file: 126 | cmds.file(newFile=True, force=True) 127 | return True 128 | -------------------------------------------------------------------------------- /python/tk_multi_workfiles/work_files.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Shotgun Software Inc. 2 | # 3 | # CONFIDENTIAL AND PROPRIETARY 4 | # 5 | # This work is provided "AS IS" and subject to the Shotgun Pipeline Toolkit 6 | # Source Code License included in this distribution package. See LICENSE. 7 | # By accessing, using, copying or modifying this work you indicate your 8 | # agreement to the Shotgun Pipeline Toolkit Source Code License. All rights 9 | # not expressly granted therein are reserved by Shotgun Software Inc. 10 | 11 | import sys 12 | import gc 13 | 14 | import sgtk 15 | from sgtk.platform.qt import QtCore 16 | 17 | from .util import report_non_destroyed_qobjects 18 | 19 | 20 | def dbg_info(func): 21 | """ 22 | Decorator function used to track memory and other useful debug information around the file-open 23 | and file-save modal dialog calls. If debug is enabled, this will print out a list of monitored 24 | QObject's that aren't destroyed correctly together with some Python memory/object stats. 25 | 26 | Note that the list of QObjects is misleading if the QApplication is set to close when the last 27 | window is closed and the dialog is the last window. 28 | """ 29 | 30 | def wrapper(*args, **kwargs): 31 | """ """ 32 | # grab the pre-run memory info: 33 | num_objects_before = len(gc.get_objects()) 34 | bytes_before = 0 35 | if sgtk.util.is_macos(): 36 | import resource 37 | 38 | bytes_before = ( 39 | resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024.0 / 1024.0 40 | ) 41 | 42 | # run the function: 43 | res = func(*args, **kwargs) 44 | 45 | # report any non-destroyed QObjects: 46 | # Note, this will usually run before the main objects have been destroyed by the 47 | # event loop so it's important to cross-check the output with subsequent lines. 48 | report_non_destroyed_qobjects() 49 | 50 | # cleanup and grab the post-run memory info: 51 | gc.collect() 52 | bytes_after = 0 53 | if sgtk.util.is_macos(): 54 | bytes_after = ( 55 | resource.getrusage(resource.RUSAGE_SELF).ru_maxrss / 1024.0 / 1024.0 56 | ) 57 | num_objects_after = len(gc.get_objects()) 58 | 59 | # and report any difference in memory usage: 60 | bytes_diff = bytes_after - bytes_before 61 | obj_diff = num_objects_after - num_objects_before 62 | msg = ( 63 | "Memory before: %0.2fMb, current: %0.2fMb, leaked: %0.2fMb (%d new Python objects)" 64 | % (bytes_before, bytes_after, bytes_diff, obj_diff) 65 | ) 66 | app = sgtk.platform.current_bundle() 67 | app.log_debug(msg) 68 | 69 | # return the result: 70 | return res 71 | 72 | return wrapper 73 | 74 | 75 | class WorkFiles(object): 76 | """ 77 | Main entry point for all commands in the app. 78 | """ 79 | 80 | def __init__(self, use_modal_dialog=False): 81 | """ 82 | Constructor. 83 | """ 84 | app = sgtk.platform.current_bundle() 85 | app.log_debug("Synchronizing remote path cache...") 86 | app.sgtk.synchronize_filesystem_structure() 87 | app.log_debug("Path cache up to date!") 88 | 89 | # If the user wants to debug the dialog, show it modally and wrap it 90 | # with memory leak-detection code. 91 | if app.use_debug_dialog: 92 | self._dialog_launcher = dbg_info(app.engine.show_modal) 93 | elif use_modal_dialog: 94 | self._dialog_launcher = app.engine.show_modal 95 | else: 96 | self._dialog_launcher = app.engine.show_dialog 97 | 98 | @staticmethod 99 | def show_file_open_dlg(use_modal_dialog=False): 100 | """ 101 | Show the file open dialog 102 | """ 103 | handler = WorkFiles(use_modal_dialog) 104 | from .file_open_form import FileOpenForm 105 | 106 | handler._show_file_dlg("File Open", FileOpenForm) 107 | 108 | @staticmethod 109 | def show_context_change_dlg(use_modal_dialog=False): 110 | """ 111 | Show the file open dialog 112 | """ 113 | handler = WorkFiles(use_modal_dialog) 114 | from .context_change_form import ContextChangeForm 115 | 116 | handler._show_file_dlg("Change Context", ContextChangeForm) 117 | 118 | @staticmethod 119 | def show_file_save_dlg(use_modal_dialog=False): 120 | """ 121 | Show the file save dialog 122 | """ 123 | handler = WorkFiles(use_modal_dialog) 124 | from .file_save_form import FileSaveForm 125 | 126 | handler._show_file_dlg("File Save", FileSaveForm) 127 | 128 | def _show_file_dlg(self, dlg_name, form, *args): 129 | """ 130 | Shows the file dialog modally or not depending on the current DCC and settings. 131 | 132 | :param dlg_name: Title of the dialog. 133 | :param form: Factory for the dialog class. 134 | """ 135 | app = sgtk.platform.current_bundle() 136 | try: 137 | self._dialog_launcher(dlg_name, app, form, *args) 138 | except: 139 | app.log_exception("Failed to create %s dialog!" % dlg_name) 140 | --------------------------------------------------------------------------------