├── 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 | [](http://www.vfxplatform.com/)
2 | [](https://www.python.org/)
3 | [](https://dev.azure.com/shotgun-ecosystem/Toolkit/_build/latest?definitionId=50&repoName=shotgunsoftware%2Ftk-multi-workfiles2&branchName=refs%2Fpull%2F91%2Fmerge)
4 | [](https://codecov.io/gh/shotgunsoftware/tk-multi-workfiles2)
5 | [](https://github.com/psf/black)
6 | [](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 |
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 |
123 | 1
124 |
125 |
126 | BreadcrumbWidget
127 | QWidget
128 |
129 | 1
130 |
131 |
132 | BrowserForm
133 | QWidget
134 |
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 |
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 |
--------------------------------------------------------------------------------