Merge branch 'develop' of github.com:pypeclub/pype into feature/delivery_in_library_loader
80
CHANGELOG.md
|
|
@ -1,5 +1,37 @@
|
|||
# Changelog
|
||||
|
||||
## [2.18.0](https://github.com/pypeclub/openpype/tree/2.18.0) (2021-05-18)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/openpype/compare/2.17.3...2.18.0)
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
- Use SubsetLoader and multiple contexts for delete_old_versions [\#1484](ttps://github.com/pypeclub/OpenPype/pull/1484))
|
||||
- TVPaint: Increment workfile version on successfull publish. [\#1489](https://github.com/pypeclub/OpenPype/pull/1489)
|
||||
- Maya: Use of multiple deadline servers [\#1483](https://github.com/pypeclub/OpenPype/pull/1483)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Use instance frame start instead of timeline. [\#1486](https://github.com/pypeclub/OpenPype/pull/1486)
|
||||
- Maya: Redshift - set proper start frame on proxy [\#1480](https://github.com/pypeclub/OpenPype/pull/1480)
|
||||
- Maya: wrong collection of playblasted frames [\#1517](https://github.com/pypeclub/OpenPype/pull/1517)
|
||||
- Existing subsets hints in creator [\#1502](https://github.com/pypeclub/OpenPype/pull/1502)
|
||||
|
||||
## [2.17.3](https://github.com/pypeclub/openpype/tree/2.17.3) (2021-05-06)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/openpype/compare/CI/3.0.0-rc.3...2.17.3)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Nuke: workfile version synced to db version always [\#1479](https://github.com/pypeclub/OpenPype/pull/1479)
|
||||
|
||||
## [2.17.2](https://github.com/pypeclub/openpype/tree/2.17.2) (2021-05-04)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/openpype/compare/CI/3.0.0-rc.1...2.17.2)
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
- Forward/Backward compatible apps and tools with OpenPype 3 [\#1463](https://github.com/pypeclub/OpenPype/pull/1463)
|
||||
|
||||
## [2.17.1](https://github.com/pypeclub/openpype/tree/2.17.1) (2021-04-30)
|
||||
|
||||
|
|
@ -7,28 +39,30 @@
|
|||
|
||||
**Enhancements:**
|
||||
|
||||
- Nuke: deadline submission with gpu [\#1414](https://github.com/pypeclub/OpenPype/pull/1414)
|
||||
- TVPaint frame range definition [\#1424](https://github.com/pypeclub/OpenPype/pull/1424)
|
||||
- PS - group all published instances [\#1415](https://github.com/pypeclub/OpenPype/pull/1415)
|
||||
- Nuke: deadline submission with gpu [\#1414](https://github.com/pypeclub/OpenPype/pull/1414)
|
||||
- Add task name to context pop up. [\#1383](https://github.com/pypeclub/OpenPype/pull/1383)
|
||||
- AE add duration validation [\#1363](https://github.com/pypeclub/OpenPype/pull/1363)
|
||||
- Maya: Support for Redshift proxies [\#1360](https://github.com/pypeclub/OpenPype/pull/1360)
|
||||
- Enhance review letterbox feature. [\#1371](https://github.com/pypeclub/OpenPype/pull/1371)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Nuke: fixing undo for loaded mov and sequence [\#1433](https://github.com/pypeclub/OpenPype/pull/1433)
|
||||
- AE - validation for duration was 1 frame shorter [\#1426](https://github.com/pypeclub/OpenPype/pull/1426)
|
||||
- Houdini menu filename [\#1417](https://github.com/pypeclub/OpenPype/pull/1417)
|
||||
- Maya: Vray - problem getting all file nodes for look publishing [\#1399](https://github.com/pypeclub/OpenPype/pull/1399)
|
||||
- AE - validation for duration was 1 frame shorter [\#1426](https://github.com/pypeclub/OpenPype/pull/1426)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Maya: Vray - problem getting all file nodes for look publishing [\#1399](https://github.com/pypeclub/OpenPype/pull/1399)
|
||||
- Maya: Support for Redshift proxies [\#1360](https://github.com/pypeclub/OpenPype/pull/1360)
|
||||
|
||||
## [2.17.0](https://github.com/pypeclub/openpype/tree/2.17.0) (2021-04-20)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/openpype/compare/3.0.0-beta2...2.17.0)
|
||||
[Full Changelog](https://github.com/pypeclub/openpype/compare/CI/3.0.0-beta.2...2.17.0)
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
- Forward compatible ftrack group [\#1243](https://github.com/pypeclub/OpenPype/pull/1243)
|
||||
- Settings in mongo as dict [\#1221](https://github.com/pypeclub/OpenPype/pull/1221)
|
||||
- Maya: Make tx option configurable with presets [\#1328](https://github.com/pypeclub/OpenPype/pull/1328)
|
||||
- TVPaint asset name validation [\#1302](https://github.com/pypeclub/OpenPype/pull/1302)
|
||||
- TV Paint: Set initial project settings. [\#1299](https://github.com/pypeclub/OpenPype/pull/1299)
|
||||
|
|
@ -56,35 +90,6 @@
|
|||
- Nuke: reverse search to make it more versatile [\#1178](https://github.com/pypeclub/OpenPype/pull/1178)
|
||||
|
||||
|
||||
## [2.16.1](https://github.com/pypeclub/pype/tree/2.16.1) (2021-04-13)
|
||||
|
||||
[Full Changelog](https://github.com/pypeclub/pype/compare/2.16.0...2.16.1)
|
||||
|
||||
**Enhancements:**
|
||||
|
||||
- Nuke: comp renders mix up [\#1301](https://github.com/pypeclub/pype/pull/1301)
|
||||
- Validate project settings [\#1297](https://github.com/pypeclub/pype/pull/1297)
|
||||
- After Effects: added SubsetManager [\#1234](https://github.com/pypeclub/pype/pull/1234)
|
||||
|
||||
**Fixed bugs:**
|
||||
|
||||
- Ftrack custom attributes in bulks [\#1312](https://github.com/pypeclub/pype/pull/1312)
|
||||
- Ftrack optional pypclub role [\#1303](https://github.com/pypeclub/pype/pull/1303)
|
||||
- AE remove orphaned instance from workfile - fix self.stub [\#1282](https://github.com/pypeclub/pype/pull/1282)
|
||||
- Avalon schema names [\#1242](https://github.com/pypeclub/pype/pull/1242)
|
||||
- Handle duplication of Task name [\#1226](https://github.com/pypeclub/pype/pull/1226)
|
||||
- Modified path of plugin loads for Harmony and TVPaint [\#1217](https://github.com/pypeclub/pype/pull/1217)
|
||||
- Regex checks in profiles filtering [\#1214](https://github.com/pypeclub/pype/pull/1214)
|
||||
- Bulk mov strict task [\#1204](https://github.com/pypeclub/pype/pull/1204)
|
||||
- Update custom ftrack session attributes [\#1202](https://github.com/pypeclub/pype/pull/1202)
|
||||
- Nuke: write node colorspace ignore `default\(\)` label [\#1199](https://github.com/pypeclub/pype/pull/1199)
|
||||
- Nuke: reverse search to make it more versatile [\#1178](https://github.com/pypeclub/pype/pull/1178)
|
||||
|
||||
**Merged pull requests:**
|
||||
|
||||
- Forward compatible ftrack group [\#1243](https://github.com/pypeclub/pype/pull/1243)
|
||||
- Error message in pyblish UI [\#1206](https://github.com/pypeclub/pype/pull/1206)
|
||||
- Nuke: deadline submission with search replaced env values from preset [\#1194](https://github.com/pypeclub/pype/pull/1194)
|
||||
|
||||
## [2.16.0](https://github.com/pypeclub/pype/tree/2.16.0) (2021-03-22)
|
||||
|
||||
|
|
@ -1145,4 +1150,7 @@ A large cleanup release. Most of the change are under the hood.
|
|||
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
||||
|
||||
|
||||
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
||||
|
||||
|
||||
\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
|
||||
|
|
|
|||
|
|
@ -224,6 +224,11 @@ def launch(app, project, asset, task,
|
|||
PypeCommands().run_application(app, project, asset, task, tools, arguments)
|
||||
|
||||
|
||||
@main.command(context_settings={"ignore_unknown_options": True})
|
||||
def projectmanager():
|
||||
PypeCommands().launch_project_manager()
|
||||
|
||||
|
||||
@main.command(
|
||||
context_settings=dict(
|
||||
ignore_unknown_options=True,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
def add_implementation_envs(env, _app):
|
||||
"""Modify environments to contain all required for implementation."""
|
||||
defaults = {
|
||||
"OPENPYPE_LOG_NO_COLORS": "True",
|
||||
"WEBSOCKET_URL": "ws://localhost:8097/ws/"
|
||||
}
|
||||
for key, value in defaults.items():
|
||||
if not env.get(key):
|
||||
env[key] = value
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import os
|
||||
|
||||
|
||||
def add_implementation_envs(env, _app):
|
||||
"""Modify environments to contain all required for implementation."""
|
||||
# Prepare path to implementation script
|
||||
implementation_user_script_path = os.path.join(
|
||||
os.environ["OPENPYPE_REPOS_ROOT"],
|
||||
"repos",
|
||||
"avalon-core",
|
||||
"setup",
|
||||
"blender"
|
||||
)
|
||||
|
||||
# Add blender implementation script path to PYTHONPATH
|
||||
python_path = env.get("PYTHONPATH") or ""
|
||||
python_path_parts = [
|
||||
path
|
||||
for path in python_path.split(os.pathsep)
|
||||
if path
|
||||
]
|
||||
python_path_parts.insert(0, implementation_user_script_path)
|
||||
env["PYTHONPATH"] = os.pathsep.join(python_path_parts)
|
||||
|
||||
# Modify Blender user scripts path
|
||||
previous_user_scripts = set()
|
||||
# Implementation path is added to set for easier paths check inside loops
|
||||
# - will be removed at the end
|
||||
previous_user_scripts.add(implementation_user_script_path)
|
||||
|
||||
openpype_blender_user_scripts = (
|
||||
env.get("OPENPYPE_BLENDER_USER_SCRIPTS") or ""
|
||||
)
|
||||
for path in openpype_blender_user_scripts.split(os.pathsep):
|
||||
if path and os.path.exists(path):
|
||||
previous_user_scripts.add(os.path.normpath(path))
|
||||
|
||||
blender_user_scripts = env.get("BLENDER_USER_SCRIPTS") or ""
|
||||
for path in blender_user_scripts.split(os.pathsep):
|
||||
if path and os.path.exists(path):
|
||||
previous_user_scripts.add(os.path.normpath(path))
|
||||
|
||||
# Remove implementation path from user script paths as is set to
|
||||
# `BLENDER_USER_SCRIPTS`
|
||||
previous_user_scripts.remove(implementation_user_script_path)
|
||||
env["BLENDER_USER_SCRIPTS"] = implementation_user_script_path
|
||||
|
||||
# Set custom user scripts env
|
||||
env["OPENPYPE_BLENDER_USER_SCRIPTS"] = os.pathsep.join(
|
||||
previous_user_scripts
|
||||
)
|
||||
|
||||
# Define Qt binding if not defined
|
||||
if not env.get("QT_PREFERRED_BINDING"):
|
||||
env["QT_PREFERRED_BINDING"] = "PySide2"
|
||||
|
|
@ -4,6 +4,8 @@ import traceback
|
|||
|
||||
import bpy
|
||||
|
||||
from .lib import append_user_scripts
|
||||
|
||||
from avalon import api as avalon
|
||||
from pyblish import api as pyblish
|
||||
|
||||
|
|
@ -29,7 +31,7 @@ def install():
|
|||
pyblish.register_plugin_path(str(PUBLISH_PATH))
|
||||
avalon.register_plugin_path(avalon.Loader, str(LOAD_PATH))
|
||||
avalon.register_plugin_path(avalon.Creator, str(CREATE_PATH))
|
||||
|
||||
append_user_scripts()
|
||||
avalon.on("new", on_new)
|
||||
avalon.on("open", on_open)
|
||||
|
||||
|
|
|
|||
127
openpype/hosts/blender/api/lib.py
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
import os
|
||||
import traceback
|
||||
import importlib
|
||||
|
||||
import bpy
|
||||
import addon_utils
|
||||
|
||||
|
||||
def load_scripts(paths):
|
||||
"""Copy of `load_scripts` from Blender's implementation.
|
||||
|
||||
It is possible that whis function will be changed in future and usage will
|
||||
be based on Blender version.
|
||||
"""
|
||||
import bpy_types
|
||||
|
||||
loaded_modules = set()
|
||||
|
||||
previous_classes = [
|
||||
cls
|
||||
for cls in bpy.types.bpy_struct.__subclasses__()
|
||||
]
|
||||
|
||||
def register_module_call(mod):
|
||||
register = getattr(mod, "register", None)
|
||||
if register:
|
||||
try:
|
||||
register()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
else:
|
||||
print("\nWarning! '%s' has no register function, "
|
||||
"this is now a requirement for registerable scripts" %
|
||||
mod.__file__)
|
||||
|
||||
def unregister_module_call(mod):
|
||||
unregister = getattr(mod, "unregister", None)
|
||||
if unregister:
|
||||
try:
|
||||
unregister()
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
def test_reload(mod):
|
||||
# reloading this causes internal errors
|
||||
# because the classes from this module are stored internally
|
||||
# possibly to refresh internal references too but for now, best not to.
|
||||
if mod == bpy_types:
|
||||
return mod
|
||||
|
||||
try:
|
||||
return importlib.reload(mod)
|
||||
except:
|
||||
traceback.print_exc()
|
||||
|
||||
def test_register(mod):
|
||||
if mod:
|
||||
register_module_call(mod)
|
||||
bpy.utils._global_loaded_modules.append(mod.__name__)
|
||||
|
||||
from bpy_restrict_state import RestrictBlend
|
||||
|
||||
with RestrictBlend():
|
||||
for base_path in paths:
|
||||
for path_subdir in bpy.utils._script_module_dirs:
|
||||
path = os.path.join(base_path, path_subdir)
|
||||
if not os.path.isdir(path):
|
||||
continue
|
||||
|
||||
bpy.utils._sys_path_ensure_prepend(path)
|
||||
|
||||
# Only add to 'sys.modules' unless this is 'startup'.
|
||||
if path_subdir != "startup":
|
||||
continue
|
||||
for mod in bpy.utils.modules_from_path(path, loaded_modules):
|
||||
test_register(mod)
|
||||
|
||||
addons_paths = []
|
||||
for base_path in paths:
|
||||
addons_path = os.path.join(base_path, "addons")
|
||||
if not os.path.exists(addons_path):
|
||||
continue
|
||||
addons_paths.append(addons_path)
|
||||
addons_module_path = os.path.join(addons_path, "modules")
|
||||
if os.path.exists(addons_module_path):
|
||||
bpy.utils._sys_path_ensure_prepend(addons_module_path)
|
||||
|
||||
if addons_paths:
|
||||
# Fake addons
|
||||
origin_paths = addon_utils.paths
|
||||
|
||||
def new_paths():
|
||||
paths = origin_paths() + addons_paths
|
||||
return paths
|
||||
|
||||
addon_utils.paths = new_paths
|
||||
addon_utils.modules_refresh()
|
||||
|
||||
# load template (if set)
|
||||
if any(bpy.utils.app_template_paths()):
|
||||
import bl_app_template_utils
|
||||
bl_app_template_utils.reset(reload_scripts=False)
|
||||
del bl_app_template_utils
|
||||
|
||||
for cls in bpy.types.bpy_struct.__subclasses__():
|
||||
if cls in previous_classes:
|
||||
continue
|
||||
if not getattr(cls, "is_registered", False):
|
||||
continue
|
||||
for subcls in cls.__subclasses__():
|
||||
if not subcls.is_registered:
|
||||
print(
|
||||
"Warning, unregistered class: %s(%s)" %
|
||||
(subcls.__name__, cls.__name__)
|
||||
)
|
||||
|
||||
|
||||
def append_user_scripts():
|
||||
user_scripts = os.environ.get("OPENPYPE_BLENDER_USER_SCRIPTS")
|
||||
if not user_scripts:
|
||||
return
|
||||
|
||||
try:
|
||||
load_scripts(user_scripts.split(os.pathsep))
|
||||
except Exception:
|
||||
print("Couldn't load user scripts \"{}\"".format(user_scripts))
|
||||
traceback.print_exc()
|
||||
|
|
@ -20,21 +20,9 @@ class CreateLayout(openpype.hosts.blender.api.plugin.Creator):
|
|||
asset = self.data["asset"]
|
||||
subset = self.data["subset"]
|
||||
name = openpype.hosts.blender.api.plugin.asset_name(asset, subset)
|
||||
collection = bpy.data.collections.new(name=name)
|
||||
bpy.context.scene.collection.children.link(collection)
|
||||
collection = bpy.context.collection
|
||||
collection.name = name
|
||||
self.data['task'] = api.Session.get('AVALON_TASK')
|
||||
lib.imprint(collection, self.data)
|
||||
|
||||
# Add the rig object and all the children meshes to
|
||||
# a set and link them all at the end to avoid duplicates.
|
||||
# Blender crashes if trying to link an object that is already linked.
|
||||
# This links automatically the children meshes if they were not
|
||||
# selected, and doesn't link them twice if they, insted,
|
||||
# were manually selected by the user.
|
||||
objects_to_link = set()
|
||||
|
||||
if (self.options or {}).get("useSelection"):
|
||||
for obj in lib.get_selection():
|
||||
collection.children.link(obj.users_collection[0])
|
||||
|
||||
return collection
|
||||
|
|
|
|||
|
|
@ -367,13 +367,13 @@ class UnrealLayoutLoader(plugin.AssetLoader):
|
|||
# Y axis mirrored
|
||||
obj.location = (
|
||||
location.get('x'),
|
||||
-location.get('y'),
|
||||
location.get('y'),
|
||||
location.get('z')
|
||||
)
|
||||
obj.rotation_euler = (
|
||||
rotation.get('x') + math.pi / 2,
|
||||
-rotation.get('y'),
|
||||
-rotation.get('z')
|
||||
rotation.get('x'),
|
||||
rotation.get('y'),
|
||||
rotation.get('z')
|
||||
)
|
||||
obj.scale = (
|
||||
scale.get('x'),
|
||||
|
|
|
|||
|
|
@ -108,19 +108,21 @@ class BlendModelLoader(plugin.AssetLoader):
|
|||
self.__class__.__name__,
|
||||
)
|
||||
|
||||
container_metadata = container.get(
|
||||
blender.pipeline.AVALON_PROPERTY)
|
||||
metadata = container.get(blender.pipeline.AVALON_PROPERTY)
|
||||
|
||||
container_metadata["libpath"] = libpath
|
||||
container_metadata["lib_container"] = lib_container
|
||||
metadata["libpath"] = libpath
|
||||
metadata["lib_container"] = lib_container
|
||||
|
||||
obj_container = self._process(
|
||||
libpath, lib_container, container_name, None)
|
||||
|
||||
container_metadata["obj_container"] = obj_container
|
||||
metadata["obj_container"] = obj_container
|
||||
|
||||
# Save the list of objects in the metadata container
|
||||
container_metadata["objects"] = obj_container.all_objects
|
||||
metadata["objects"] = obj_container.all_objects
|
||||
|
||||
metadata["parent"] = str(context["representation"]["parent"])
|
||||
metadata["family"] = context["representation"]["context"]["family"]
|
||||
|
||||
nodes = list(container.objects)
|
||||
nodes.append(container)
|
||||
|
|
|
|||
|
|
@ -155,18 +155,20 @@ class BlendRigLoader(plugin.AssetLoader):
|
|||
self.__class__.__name__,
|
||||
)
|
||||
|
||||
container_metadata = container.get(
|
||||
blender.pipeline.AVALON_PROPERTY)
|
||||
metadata = container.get(blender.pipeline.AVALON_PROPERTY)
|
||||
|
||||
container_metadata["libpath"] = libpath
|
||||
container_metadata["lib_container"] = lib_container
|
||||
metadata["libpath"] = libpath
|
||||
metadata["lib_container"] = lib_container
|
||||
|
||||
obj_container = self._process(
|
||||
libpath, lib_container, collection_name, None, None)
|
||||
|
||||
container_metadata["obj_container"] = obj_container
|
||||
metadata["obj_container"] = obj_container
|
||||
# Save the list of objects in the metadata container
|
||||
container_metadata["objects"] = obj_container.all_objects
|
||||
metadata["objects"] = obj_container.all_objects
|
||||
|
||||
metadata["parent"] = str(context["representation"]["parent"])
|
||||
metadata["family"] = context["representation"]["context"]["family"]
|
||||
|
||||
nodes = list(container.objects)
|
||||
nodes.append(container)
|
||||
|
|
|
|||
92
openpype/hosts/blender/plugins/publish/extract_layout.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import os
|
||||
import json
|
||||
|
||||
import bpy
|
||||
|
||||
from avalon import blender, io
|
||||
import openpype.api
|
||||
|
||||
|
||||
class ExtractLayout(openpype.api.Extractor):
|
||||
"""Extract a layout."""
|
||||
|
||||
label = "Extract Layout"
|
||||
hosts = ["blender"]
|
||||
families = ["layout"]
|
||||
optional = True
|
||||
|
||||
def process(self, instance):
|
||||
# Define extract output file path
|
||||
stagingdir = self.staging_dir(instance)
|
||||
|
||||
# Perform extraction
|
||||
self.log.info("Performing extraction..")
|
||||
|
||||
json_data = []
|
||||
|
||||
for collection in instance:
|
||||
for asset in collection.children:
|
||||
collection = bpy.data.collections[asset.name]
|
||||
container = bpy.data.collections[asset.name + '_CON']
|
||||
metadata = container.get(blender.pipeline.AVALON_PROPERTY)
|
||||
|
||||
parent = metadata["parent"]
|
||||
family = metadata["family"]
|
||||
|
||||
self.log.debug("Parent: {}".format(parent))
|
||||
blend = io.find_one(
|
||||
{
|
||||
"type": "representation",
|
||||
"parent": io.ObjectId(parent),
|
||||
"name": "blend"
|
||||
},
|
||||
projection={"_id": True})
|
||||
blend_id = blend["_id"]
|
||||
|
||||
json_element = {}
|
||||
json_element["reference"] = str(blend_id)
|
||||
json_element["family"] = family
|
||||
json_element["instance_name"] = asset.name
|
||||
json_element["asset_name"] = metadata["lib_container"]
|
||||
json_element["file_path"] = metadata["libpath"]
|
||||
|
||||
obj = collection.objects[0]
|
||||
|
||||
json_element["transform"] = {
|
||||
"translation": {
|
||||
"x": obj.location.x,
|
||||
"y": obj.location.y,
|
||||
"z": obj.location.z
|
||||
},
|
||||
"rotation": {
|
||||
"x": obj.rotation_euler.x,
|
||||
"y": obj.rotation_euler.y,
|
||||
"z": obj.rotation_euler.z,
|
||||
},
|
||||
"scale": {
|
||||
"x": obj.scale.x,
|
||||
"y": obj.scale.y,
|
||||
"z": obj.scale.z
|
||||
}
|
||||
}
|
||||
json_data.append(json_element)
|
||||
|
||||
json_filename = "{}.json".format(instance.name)
|
||||
json_path = os.path.join(stagingdir, json_filename)
|
||||
|
||||
with open(json_path, "w+") as file:
|
||||
json.dump(json_data, fp=file, indent=2)
|
||||
|
||||
if "representations" not in instance.data:
|
||||
instance.data["representations"] = []
|
||||
|
||||
representation = {
|
||||
'name': 'json',
|
||||
'ext': 'json',
|
||||
'files': json_filename,
|
||||
"stagingDir": stagingdir,
|
||||
}
|
||||
instance.data["representations"].append(representation)
|
||||
|
||||
self.log.info("Extracted instance '%s' to: %s",
|
||||
instance.name, representation)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import os
|
||||
|
||||
|
||||
def add_implementation_envs(env, _app):
|
||||
"""Modify environments to contain all required for implementation."""
|
||||
openharmony_path = os.path.join(
|
||||
os.environ["OPENPYPE_REPOS_ROOT"], "pype", "vendor", "OpenHarmony"
|
||||
)
|
||||
# TODO check if is already set? What to do if is already set?
|
||||
env["LIB_OPENHARMONY_PATH"] = openharmony_path
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
import os
|
||||
import platform
|
||||
|
||||
|
||||
def add_implementation_envs(env, _app):
|
||||
# Add requirements to HIERO_PLUGIN_PATH
|
||||
pype_root = os.environ["OPENPYPE_REPOS_ROOT"]
|
||||
new_hiero_paths = [
|
||||
os.path.join(pype_root, "openpype", "hosts", "hiero", "startup")
|
||||
]
|
||||
old_hiero_path = env.get("HIERO_PLUGIN_PATH") or ""
|
||||
for path in old_hiero_path.split(os.pathsep):
|
||||
if not path or not os.path.exists(path):
|
||||
continue
|
||||
|
||||
norm_path = os.path.normpath(path)
|
||||
if norm_path not in new_hiero_paths:
|
||||
new_hiero_paths.append(norm_path)
|
||||
|
||||
env["HIERO_PLUGIN_PATH"] = os.pathsep.join(new_hiero_paths)
|
||||
|
||||
# Try to add QuickTime to PATH
|
||||
quick_time_path = "C:/Program Files (x86)/QuickTime/QTSystem"
|
||||
if platform.system() == "windows" and os.path.exists(quick_time_path):
|
||||
path_value = env.get("PATH") or ""
|
||||
path_paths = [
|
||||
path
|
||||
for path in path_value.split(os.pathsep)
|
||||
if path
|
||||
]
|
||||
path_paths.append(quick_time_path)
|
||||
env["PATH"] = os.pathsep.join(path_paths)
|
||||
|
||||
# Set default values if are not already set via settings
|
||||
defaults = {
|
||||
"LOGLEVEL": "DEBUG"
|
||||
}
|
||||
for key, value in defaults.items():
|
||||
if not env.get(key):
|
||||
env[key] = value
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import os
|
||||
|
||||
|
||||
def add_implementation_envs(env, _app):
|
||||
# Add requirements to HOUDINI_PATH and HOUDINI_MENU_PATH
|
||||
pype_root = os.environ["OPENPYPE_REPOS_ROOT"]
|
||||
|
||||
startup_path = os.path.join(
|
||||
pype_root, "openpype", "hosts", "houdini", "startup"
|
||||
)
|
||||
new_houdini_path = [startup_path]
|
||||
new_houdini_menu_path = [startup_path]
|
||||
|
||||
old_houdini_path = env.get("HOUDINI_PATH") or ""
|
||||
old_houdini_menu_path = env.get("HOUDINI_MENU_PATH") or ""
|
||||
|
||||
for path in old_houdini_path.split(os.pathsep):
|
||||
if not path or not os.path.exists(path):
|
||||
continue
|
||||
|
||||
norm_path = os.path.normpath(path)
|
||||
if norm_path not in new_houdini_path:
|
||||
new_houdini_path.append(norm_path)
|
||||
|
||||
for path in old_houdini_menu_path.split(os.pathsep):
|
||||
if not path or not os.path.exists(path):
|
||||
continue
|
||||
|
||||
norm_path = os.path.normpath(path)
|
||||
if norm_path not in new_houdini_menu_path:
|
||||
new_houdini_menu_path.append(norm_path)
|
||||
|
||||
# Add ampersand for unknown reason (Maybe is needed in Houdini?)
|
||||
new_houdini_path.append("&")
|
||||
new_houdini_menu_path.append("&")
|
||||
|
||||
env["HOUDINI_PATH"] = os.pathsep.join(new_houdini_path)
|
||||
env["HOUDINI_MENU_PATH"] = os.pathsep.join(new_houdini_menu_path)
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
import os
|
||||
|
||||
|
||||
def add_implementation_envs(env, _app):
|
||||
# Add requirements to PYTHONPATH
|
||||
pype_root = os.environ["OPENPYPE_REPOS_ROOT"]
|
||||
new_python_paths = [
|
||||
os.path.join(pype_root, "openpype", "hosts", "maya", "startup"),
|
||||
os.path.join(pype_root, "repos", "avalon-core", "setup", "maya"),
|
||||
os.path.join(pype_root, "tools", "mayalookassigner")
|
||||
]
|
||||
old_python_path = env.get("PYTHONPATH") or ""
|
||||
for path in old_python_path.split(os.pathsep):
|
||||
if not path or not os.path.exists(path):
|
||||
continue
|
||||
|
||||
norm_path = os.path.normpath(path)
|
||||
if norm_path not in new_python_paths:
|
||||
new_python_paths.append(norm_path)
|
||||
|
||||
env["PYTHONPATH"] = os.pathsep.join(new_python_paths)
|
||||
|
||||
# Set default values if are not already set via settings
|
||||
defaults = {
|
||||
"OPENPYPE_LOG_NO_COLORS": "Yes"
|
||||
}
|
||||
for key, value in defaults.items():
|
||||
if not env.get(key):
|
||||
env[key] = value
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
import os
|
||||
import platform
|
||||
|
||||
|
||||
def add_implementation_envs(env, _app):
|
||||
# Add requirements to NUKE_PATH
|
||||
pype_root = os.environ["OPENPYPE_REPOS_ROOT"]
|
||||
new_nuke_paths = [
|
||||
os.path.join(pype_root, "openpype", "hosts", "nuke", "startup"),
|
||||
os.path.join(
|
||||
pype_root, "repos", "avalon-core", "setup", "nuke", "nuke_path"
|
||||
)
|
||||
]
|
||||
old_nuke_path = env.get("NUKE_PATH") or ""
|
||||
for path in old_nuke_path.split(os.pathsep):
|
||||
if not path or not os.path.exists(path):
|
||||
continue
|
||||
|
||||
norm_path = os.path.normpath(path)
|
||||
if norm_path not in new_nuke_paths:
|
||||
new_nuke_paths.append(norm_path)
|
||||
|
||||
env["NUKE_PATH"] = os.pathsep.join(new_nuke_paths)
|
||||
|
||||
# Try to add QuickTime to PATH
|
||||
quick_time_path = "C:/Program Files (x86)/QuickTime/QTSystem"
|
||||
if platform.system() == "windows" and os.path.exists(quick_time_path):
|
||||
path_value = env.get("PATH") or ""
|
||||
path_paths = [
|
||||
path
|
||||
for path in path_value.split(os.pathsep)
|
||||
if path
|
||||
]
|
||||
path_paths.append(quick_time_path)
|
||||
env["PATH"] = os.pathsep.join(path_paths)
|
||||
|
||||
# Set default values if are not already set via settings
|
||||
defaults = {
|
||||
"LOGLEVEL": "DEBUG"
|
||||
}
|
||||
for key, value in defaults.items():
|
||||
if not env.get(key):
|
||||
env[key] = value
|
||||
|
|
@ -53,11 +53,10 @@ def get_created_node_imageio_setting(**kwarg):
|
|||
imageio_node = None
|
||||
for node in imageio_nodes:
|
||||
log.info(node)
|
||||
if (node["nukeNodeClass"] != nodeclass) and (
|
||||
creator not in node["plugins"]):
|
||||
continue
|
||||
|
||||
imageio_node = node
|
||||
if (nodeclass in node["nukeNodeClass"]) and (
|
||||
creator in node["plugins"]):
|
||||
imageio_node = node
|
||||
break
|
||||
|
||||
log.info("ImageIO node: {}".format(imageio_node))
|
||||
return imageio_node
|
||||
|
|
@ -340,9 +339,9 @@ def create_write_node(name, data, input=None, prenodes=None, review=True):
|
|||
nuke.message(msg)
|
||||
|
||||
# build file path to workfiles
|
||||
fpath = str(anatomy_filled["work"]["folder"]).replace("\\", "/")
|
||||
fdir = str(anatomy_filled["work"]["folder"]).replace("\\", "/")
|
||||
fpath = data["fpath_template"].format(
|
||||
work=fpath, version=data["version"], subset=data["subset"],
|
||||
work=fdir, version=data["version"], subset=data["subset"],
|
||||
frame=data["frame"],
|
||||
ext=representation
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
def add_implementation_envs(env, _app):
|
||||
"""Modify environments to contain all required for implementation."""
|
||||
defaults = {
|
||||
"OPENPYPE_LOG_NO_COLORS": "True",
|
||||
"WEBSOCKET_URL": "ws://localhost:8099/ws/"
|
||||
}
|
||||
for key, value in defaults.items():
|
||||
if not env.get(key):
|
||||
env[key] = value
|
||||
|
|
@ -9,6 +9,7 @@ class CreateImage(openpype.api.Creator):
|
|||
name = "imageDefault"
|
||||
label = "Image"
|
||||
family = "image"
|
||||
defaults = ["Main"]
|
||||
|
||||
def process(self):
|
||||
groups = []
|
||||
|
|
@ -16,7 +17,9 @@ class CreateImage(openpype.api.Creator):
|
|||
create_group = False
|
||||
|
||||
stub = photoshop.stub()
|
||||
useSelection = False
|
||||
if (self.options or {}).get("useSelection"):
|
||||
useSelection = True
|
||||
multiple_instances = False
|
||||
selection = stub.get_selected_layers()
|
||||
self.log.info("selection {}".format(selection))
|
||||
|
|
@ -61,7 +64,9 @@ class CreateImage(openpype.api.Creator):
|
|||
# No selection creates an empty group.
|
||||
create_group = True
|
||||
else:
|
||||
create_group = True
|
||||
stub.select_layers(stub.get_layers())
|
||||
group = stub.group_selected_layers(self.name)
|
||||
groups.append(group)
|
||||
|
||||
if create_group:
|
||||
group = stub.create_group(self.name)
|
||||
|
|
@ -77,13 +82,19 @@ class CreateImage(openpype.api.Creator):
|
|||
group.name = group.name.replace(stub.PUBLISH_ICON, ''). \
|
||||
replace(stub.LOADED_ICON, '')
|
||||
|
||||
if useSelection:
|
||||
subset_name = self.data["subset"] + group.name
|
||||
else:
|
||||
# use value provided by user from Creator
|
||||
subset_name = self.data["subset"]
|
||||
|
||||
if group.long_name:
|
||||
for directory in group.long_name[::-1]:
|
||||
name = directory.replace(stub.PUBLISH_ICON, '').\
|
||||
replace(stub.LOADED_ICON, '')
|
||||
long_names.append(name)
|
||||
|
||||
self.data.update({"subset": "image" + group.name})
|
||||
self.data.update({"subset": subset_name})
|
||||
self.data.update({"uuid": str(group.id)})
|
||||
self.data.update({"long_name": "_".join(long_names)})
|
||||
stub.imprint(group, self.data)
|
||||
|
|
|
|||
|
|
@ -35,21 +35,16 @@ class ExtractImage(openpype.api.Extractor):
|
|||
if layer.visible and layer.id not in extract_ids:
|
||||
stub.set_visible(layer.id, False)
|
||||
|
||||
save_options = []
|
||||
if "png" in self.formats:
|
||||
save_options.append('png')
|
||||
if "jpg" in self.formats:
|
||||
save_options.append('jpg')
|
||||
|
||||
file_basename = os.path.splitext(
|
||||
stub.get_active_document_name()
|
||||
)[0]
|
||||
for extension in save_options:
|
||||
for extension in self.formats:
|
||||
_filename = "{}.{}".format(file_basename, extension)
|
||||
files[extension] = _filename
|
||||
|
||||
full_filename = os.path.join(staging_dir, _filename)
|
||||
stub.saveAs(full_filename, extension, True)
|
||||
self.log.info(f"Extracted: {extension}")
|
||||
|
||||
representations = []
|
||||
for extension, filename in files.items():
|
||||
|
|
|
|||
|
|
@ -6,7 +6,12 @@ from avalon import photoshop
|
|||
|
||||
|
||||
class ExtractReview(openpype.api.Extractor):
|
||||
"""Produce a flattened image file from all instances."""
|
||||
"""
|
||||
Produce a flattened image file from all 'image' instances.
|
||||
|
||||
If no 'image' instance is created, it produces flattened image from
|
||||
all visible layers.
|
||||
"""
|
||||
|
||||
label = "Extract Review"
|
||||
hosts = ["photoshop"]
|
||||
|
|
@ -30,14 +35,15 @@ class ExtractReview(openpype.api.Extractor):
|
|||
)
|
||||
output_image_path = os.path.join(staging_dir, output_image)
|
||||
with photoshop.maintained_visibility():
|
||||
# Hide all other layers.
|
||||
extract_ids = set([ll.id for ll in stub.
|
||||
get_layers_in_layers(layers)])
|
||||
self.log.info("extract_ids {}".format(extract_ids))
|
||||
for layer in stub.get_layers():
|
||||
# limit unnecessary calls to client
|
||||
if layer.visible and layer.id not in extract_ids:
|
||||
stub.set_visible(layer.id, False)
|
||||
if layers:
|
||||
# Hide all other layers.
|
||||
extract_ids = set([ll.id for ll in stub.
|
||||
get_layers_in_layers(layers)])
|
||||
self.log.debug("extract_ids {}".format(extract_ids))
|
||||
for layer in stub.get_layers():
|
||||
# limit unnecessary calls to client
|
||||
if layer.visible and layer.id not in extract_ids:
|
||||
stub.set_visible(layer.id, False)
|
||||
|
||||
stub.saveAs(output_image_path, 'jpg', True)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
def add_implementation_envs(env, _app):
|
||||
"""Modify environments to contain all required for implementation."""
|
||||
defaults = {
|
||||
"OPENPYPE_LOG_NO_COLORS": "True"
|
||||
}
|
||||
for key, value in defaults.items():
|
||||
if not env.get(key):
|
||||
env[key] = value
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import os
|
||||
|
||||
|
||||
def add_implementation_envs(env, _app):
|
||||
"""Modify environments to contain all required for implementation."""
|
||||
# Set AVALON_UNREAL_PLUGIN required for Unreal implementation
|
||||
unreal_plugin_path = os.path.join(
|
||||
os.environ["OPENPYPE_REPOS_ROOT"], "repos", "avalon-unreal-integration"
|
||||
)
|
||||
env["AVALON_UNREAL_PLUGIN"] = unreal_plugin_path
|
||||
|
||||
# Set default environments if are not set via settings
|
||||
defaults = {
|
||||
"OPENPYPE_LOG_NO_COLORS": "True"
|
||||
}
|
||||
for key, value in defaults.items():
|
||||
if not env.get(key):
|
||||
env[key] = value
|
||||
|
|
@ -78,14 +78,14 @@ class ExtractLayout(openpype.api.Extractor):
|
|||
|
||||
json_element["transform"] = {
|
||||
"translation": {
|
||||
"x": transform.translation.x,
|
||||
"x": -transform.translation.x,
|
||||
"y": transform.translation.y,
|
||||
"z": transform.translation.z
|
||||
},
|
||||
"rotation": {
|
||||
"x": math.radians(transform.rotation.euler().x),
|
||||
"x": math.radians(transform.rotation.euler().x + 90.0),
|
||||
"y": math.radians(transform.rotation.euler().y),
|
||||
"z": math.radians(transform.rotation.euler().z),
|
||||
"z": math.radians(180.0 - transform.rotation.euler().z)
|
||||
},
|
||||
"scale": {
|
||||
"x": transform.scale3d.x,
|
||||
|
|
|
|||
|
|
@ -58,6 +58,10 @@ from .python_module_tools import (
|
|||
)
|
||||
|
||||
from .avalon_context import (
|
||||
CURRENT_DOC_SCHEMAS,
|
||||
PROJECT_NAME_ALLOWED_SYMBOLS,
|
||||
PROJECT_NAME_REGEX,
|
||||
create_project,
|
||||
is_latest,
|
||||
any_outdated,
|
||||
get_asset,
|
||||
|
|
@ -163,6 +167,10 @@ __all__ = [
|
|||
"recursive_bases_from_class",
|
||||
"classes_from_module",
|
||||
|
||||
"CURRENT_DOC_SCHEMAS",
|
||||
"PROJECT_NAME_ALLOWED_SYMBOLS",
|
||||
"PROJECT_NAME_REGEX",
|
||||
"create_project",
|
||||
"is_latest",
|
||||
"any_outdated",
|
||||
"get_asset",
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import re
|
|||
import copy
|
||||
import json
|
||||
import platform
|
||||
import getpass
|
||||
import collections
|
||||
import inspect
|
||||
import subprocess
|
||||
|
|
@ -362,7 +361,6 @@ class ApplicationManager:
|
|||
context = ApplicationLaunchContext(
|
||||
app, executable, **data
|
||||
)
|
||||
# TODO pass context through launch hooks
|
||||
return context.launch()
|
||||
|
||||
|
||||
|
|
@ -626,7 +624,7 @@ class ApplicationLaunchContext:
|
|||
|
||||
# Logger
|
||||
logger_name = "{}-{}".format(self.__class__.__name__, self.app_name)
|
||||
self.log = PypeLogger().get_logger(logger_name)
|
||||
self.log = PypeLogger.get_logger(logger_name)
|
||||
|
||||
self.executable = executable
|
||||
|
||||
|
|
@ -1033,7 +1031,7 @@ def _merge_env(env, current_env):
|
|||
return result
|
||||
|
||||
|
||||
def prepare_host_environments(data):
|
||||
def prepare_host_environments(data, implementation_envs=True):
|
||||
"""Modify launch environments based on launched app and context.
|
||||
|
||||
Args:
|
||||
|
|
@ -1089,7 +1087,24 @@ def prepare_host_environments(data):
|
|||
# Merge dictionaries
|
||||
env_values = _merge_env(tool_env, env_values)
|
||||
|
||||
final_env = _merge_env(acre.compute(env_values), data["env"])
|
||||
loaded_env = _merge_env(acre.compute(env_values), data["env"])
|
||||
|
||||
final_env = None
|
||||
# Add host specific environments
|
||||
if app.host_name and implementation_envs:
|
||||
module = __import__("openpype.hosts", fromlist=[app.host_name])
|
||||
host_module = getattr(module, app.host_name, None)
|
||||
add_implementation_envs = None
|
||||
if host_module:
|
||||
add_implementation_envs = getattr(
|
||||
host_module, "add_implementation_envs", None
|
||||
)
|
||||
if add_implementation_envs:
|
||||
# Function may only modify passed dict without returning value
|
||||
final_env = add_implementation_envs(loaded_env, app)
|
||||
|
||||
if final_env is None:
|
||||
final_env = loaded_env
|
||||
|
||||
# Update env
|
||||
data["env"].update(final_env)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,99 @@ avalon = None
|
|||
log = logging.getLogger("AvalonContext")
|
||||
|
||||
|
||||
CURRENT_DOC_SCHEMAS = {
|
||||
"project": "openpype:project-3.0",
|
||||
"asset": "openpype:asset-3.0",
|
||||
"config": "openpype:config-2.0"
|
||||
}
|
||||
PROJECT_NAME_ALLOWED_SYMBOLS = "a-zA-Z0-9_"
|
||||
PROJECT_NAME_REGEX = re.compile(
|
||||
"^[{}]+$".format(PROJECT_NAME_ALLOWED_SYMBOLS)
|
||||
)
|
||||
|
||||
|
||||
def create_project(
|
||||
project_name, project_code, library_project=False, dbcon=None
|
||||
):
|
||||
"""Create project using OpenPype settings.
|
||||
|
||||
This project creation function is not validating project document on
|
||||
creation. It is because project document is created blindly with only
|
||||
minimum required information about project which is it's name, code, type
|
||||
and schema.
|
||||
|
||||
Entered project name must be unique and project must not exist yet.
|
||||
|
||||
Args:
|
||||
project_name(str): New project name. Should be unique.
|
||||
project_code(str): Project's code should be unique too.
|
||||
library_project(bool): Project is library project.
|
||||
dbcon(AvalonMongoDB): Object of connection to MongoDB.
|
||||
|
||||
Raises:
|
||||
ValueError: When project name already exists in MongoDB.
|
||||
|
||||
Returns:
|
||||
dict: Created project document.
|
||||
"""
|
||||
|
||||
from openpype.settings import ProjectSettings, SaveWarningExc
|
||||
from avalon.api import AvalonMongoDB
|
||||
from avalon.schema import validate
|
||||
|
||||
if dbcon is None:
|
||||
dbcon = AvalonMongoDB()
|
||||
|
||||
if not PROJECT_NAME_REGEX.match(project_name):
|
||||
raise ValueError((
|
||||
"Project name \"{}\" contain invalid characters"
|
||||
).format(project_name))
|
||||
|
||||
database = dbcon.database
|
||||
project_doc = database[project_name].find_one(
|
||||
{"type": "project"},
|
||||
{"name": 1}
|
||||
)
|
||||
if project_doc:
|
||||
raise ValueError("Project with name \"{}\" already exists".format(
|
||||
project_name
|
||||
))
|
||||
|
||||
project_doc = {
|
||||
"type": "project",
|
||||
"name": project_name,
|
||||
"data": {
|
||||
"code": project_code,
|
||||
"library_project": library_project
|
||||
},
|
||||
"schema": CURRENT_DOC_SCHEMAS["project"]
|
||||
}
|
||||
# Insert document with basic data
|
||||
database[project_name].insert_one(project_doc)
|
||||
# Load ProjectSettings for the project and save it to store all attributes
|
||||
# and Anatomy
|
||||
try:
|
||||
project_settings_entity = ProjectSettings(project_name)
|
||||
project_settings_entity.save()
|
||||
except SaveWarningExc as exc:
|
||||
print(str(exc))
|
||||
except Exception:
|
||||
database[project_name].delete_one({"type": "project"})
|
||||
raise
|
||||
|
||||
project_doc = database[project_name].find_one({"type": "project"})
|
||||
|
||||
try:
|
||||
# Validate created project document
|
||||
validate(project_doc)
|
||||
except Exception:
|
||||
# Remove project if is not valid
|
||||
database[project_name].delete_one({"type": "project"})
|
||||
raise
|
||||
|
||||
return project_doc
|
||||
|
||||
|
||||
def with_avalon(func):
|
||||
@functools.wraps(func)
|
||||
def wrap_avalon(*args, **kwargs):
|
||||
|
|
|
|||
|
|
@ -26,9 +26,7 @@ from openpype.modules.ftrack.lib import (
|
|||
|
||||
BaseEvent
|
||||
)
|
||||
from openpype.modules.ftrack.lib.avalon_sync import (
|
||||
EntitySchemas
|
||||
)
|
||||
from openpype.lib import CURRENT_DOC_SCHEMAS
|
||||
|
||||
|
||||
class SyncToAvalonEvent(BaseEvent):
|
||||
|
|
@ -1128,7 +1126,7 @@ class SyncToAvalonEvent(BaseEvent):
|
|||
"_id": mongo_id,
|
||||
"name": name,
|
||||
"type": "asset",
|
||||
"schema": EntitySchemas["asset"],
|
||||
"schema": CURRENT_DOC_SCHEMAS["asset"],
|
||||
"parent": proj["_id"],
|
||||
"data": {
|
||||
"ftrackId": ftrack_ent["id"],
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ log = Logger.get_logger(__name__)
|
|||
|
||||
|
||||
# Current schemas for avalon types
|
||||
EntitySchemas = {
|
||||
CURRENT_DOC_SCHEMAS = {
|
||||
"project": "openpype:project-3.0",
|
||||
"asset": "openpype:asset-3.0",
|
||||
"config": "openpype:config-2.0"
|
||||
|
|
@ -1862,7 +1862,7 @@ class SyncEntitiesFactory:
|
|||
|
||||
item["_id"] = new_id
|
||||
item["parent"] = self.avalon_project_id
|
||||
item["schema"] = EntitySchemas["asset"]
|
||||
item["schema"] = CURRENT_DOC_SCHEMAS["asset"]
|
||||
item["data"]["visualParent"] = avalon_parent
|
||||
|
||||
new_id_str = str(new_id)
|
||||
|
|
@ -2003,8 +2003,8 @@ class SyncEntitiesFactory:
|
|||
|
||||
project_item["_id"] = new_id
|
||||
project_item["parent"] = None
|
||||
project_item["schema"] = EntitySchemas["project"]
|
||||
project_item["config"]["schema"] = EntitySchemas["config"]
|
||||
project_item["schema"] = CURRENT_DOC_SCHEMAS["project"]
|
||||
project_item["config"]["schema"] = CURRENT_DOC_SCHEMAS["config"]
|
||||
|
||||
self.ftrack_avalon_mapper[self.ft_project_id] = new_id
|
||||
self.avalon_ftrack_mapper[new_id] = self.ft_project_id
|
||||
|
|
|
|||
|
|
@ -170,7 +170,10 @@ class LocalDriveHandler(AbstractProvider):
|
|||
site=site,
|
||||
progress=status_val
|
||||
)
|
||||
target_file_size = os.path.getsize(target_path)
|
||||
try:
|
||||
target_file_size = os.path.getsize(target_path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
time.sleep(0.5)
|
||||
|
||||
def _normalize_site_name(self, site_name):
|
||||
|
|
|
|||
|
|
@ -501,6 +501,8 @@ class SyncServerModule(PypeModule, ITrayModule):
|
|||
items = self.get_configurable_items_for_site(project_name,
|
||||
site_name,
|
||||
scope)
|
||||
# Local Settings need 'local' instead of real value
|
||||
site_name = site_name.replace(get_local_site_id(), 'local')
|
||||
editable[site_name] = items
|
||||
|
||||
return editable
|
||||
|
|
@ -591,8 +593,6 @@ class SyncServerModule(PypeModule, ITrayModule):
|
|||
else:
|
||||
item["value"] = val
|
||||
|
||||
|
||||
|
||||
editable.append(item)
|
||||
|
||||
return editable
|
||||
|
|
@ -877,7 +877,7 @@ class SyncServerModule(PypeModule, ITrayModule):
|
|||
}
|
||||
all_sites = {self.DEFAULT_SITE: studio_config}
|
||||
if sync_enabled:
|
||||
all_sites['local'] = {'provider': 'local_drive'}
|
||||
all_sites[get_local_site_id()] = {'provider': 'local_drive'}
|
||||
return all_sites
|
||||
|
||||
def get_provider_for_site(self, project_name=None, site=None):
|
||||
|
|
|
|||
|
|
@ -65,12 +65,19 @@ class CollectAnatomyContextData(pyblish.api.ContextPlugin):
|
|||
"username": context.data["user"]
|
||||
}
|
||||
|
||||
app_manager = ApplicationManager()
|
||||
app_name = os.environ.get("AVALON_APP_NAME")
|
||||
if app_name:
|
||||
app = app_manager.applications.get(app_name)
|
||||
if app:
|
||||
context_data["app"] = app.host_name
|
||||
# Use AVALON_APP as first if available it is the same as host name
|
||||
# - only if is not defined use AVALON_APP_NAME (e.g. on Farm) and
|
||||
# set it back to AVALON_APP env variable
|
||||
host_name = os.environ.get("AVALON_APP")
|
||||
if not host_name:
|
||||
app_manager = ApplicationManager()
|
||||
app_name = os.environ.get("AVALON_APP_NAME")
|
||||
if app_name:
|
||||
app = app_manager.applications.get(app_name)
|
||||
if app:
|
||||
host_name = app.host_name
|
||||
os.environ["AVALON_APP"] = host_name
|
||||
context_data["app"] = host_name
|
||||
|
||||
datetime_data = context.data.get("datetimeData") or {}
|
||||
context_data.update(datetime_data)
|
||||
|
|
|
|||
|
|
@ -110,6 +110,12 @@ class PypeCommands:
|
|||
with open(output_json_path, "w") as file_stream:
|
||||
json.dump(env, file_stream, indent=4)
|
||||
|
||||
@staticmethod
|
||||
def launch_project_manager():
|
||||
from openpype.tools import project_manager
|
||||
|
||||
project_manager.main()
|
||||
|
||||
def texture_copy(self, project, asset, path):
|
||||
pass
|
||||
|
||||
|
|
|
|||
17
openpype/settings/defaults/project_settings/photoshop.json
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"create": {
|
||||
"CreateImage": {
|
||||
"defaults": [
|
||||
"Main"
|
||||
]
|
||||
}
|
||||
},
|
||||
"publish": {
|
||||
"ExtractImage": {
|
||||
"formats": [
|
||||
"png",
|
||||
"jpg"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -16,5 +16,21 @@
|
|||
"active": true
|
||||
}
|
||||
},
|
||||
"load": {
|
||||
"LoadImage": {
|
||||
"defaults": {
|
||||
"stretch": true,
|
||||
"timestretch": true,
|
||||
"preload": true
|
||||
}
|
||||
},
|
||||
"ImportImage": {
|
||||
"defaults": {
|
||||
"stretch": true,
|
||||
"timestretch": true,
|
||||
"preload": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"filters": {}
|
||||
}
|
||||
|
|
@ -5,18 +5,11 @@
|
|||
"icon": "{}/app_icons/maya.png",
|
||||
"host_name": "maya",
|
||||
"environment": {
|
||||
"PYTHONPATH": [
|
||||
"{OPENPYPE_REPOS_ROOT}/openpype/hosts/maya/startup",
|
||||
"{OPENPYPE_REPOS_ROOT}/repos/avalon-core/setup/maya",
|
||||
"{OPENPYPE_REPOS_ROOT}/repos/maya-look-assigner",
|
||||
"{PYTHONPATH}"
|
||||
],
|
||||
"MAYA_DISABLE_CLIC_IPM": "Yes",
|
||||
"MAYA_DISABLE_CIP": "Yes",
|
||||
"MAYA_DISABLE_CER": "Yes",
|
||||
"PYMEL_SKIP_MEL_INIT": "Yes",
|
||||
"LC_ALL": "C",
|
||||
"OPENPYPE_LOG_NO_COLORS": "Yes"
|
||||
"LC_ALL": "C"
|
||||
},
|
||||
"variants": {
|
||||
"2022": {
|
||||
|
|
@ -110,15 +103,7 @@
|
|||
"icon": "{}/app_icons/nuke.png",
|
||||
"host_name": "nuke",
|
||||
"environment": {
|
||||
"NUKE_PATH": [
|
||||
"{OPENPYPE_REPOS_ROOT}/repos/avalon-core/setup/nuke/nuke_path",
|
||||
"{OPENPYPE_REPOS_ROOT}/openpype/hosts/nuke/startup",
|
||||
"{OPENPYPE_STUDIO_PLUGINS}/nuke"
|
||||
],
|
||||
"PATH": {
|
||||
"windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}"
|
||||
},
|
||||
"LOGLEVEL": "DEBUG"
|
||||
"NUKE_PATH": "{OPENPYPE_STUDIO_PLUGINS}/nuke"
|
||||
},
|
||||
"variants": {
|
||||
"13-0": {
|
||||
|
|
@ -224,15 +209,7 @@
|
|||
"icon": "{}/app_icons/nuke.png",
|
||||
"host_name": "nuke",
|
||||
"environment": {
|
||||
"NUKE_PATH": [
|
||||
"{OPENPYPE_REPOS_ROOT}/repos/avalon-core/setup/nuke/nuke_path",
|
||||
"{OPENPYPE_REPOS_ROOT}/openpype/hosts/nuke/startup",
|
||||
"{OPENPYPE_STUDIO_PLUGINS}/nuke"
|
||||
],
|
||||
"PATH": {
|
||||
"windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}"
|
||||
},
|
||||
"LOGLEVEL": "DEBUG"
|
||||
"NUKE_PATH": "{OPENPYPE_STUDIO_PLUGINS}/nuke"
|
||||
},
|
||||
"variants": {
|
||||
"13-0": {
|
||||
|
|
@ -368,15 +345,8 @@
|
|||
"icon": "{}/app_icons/nuke.png",
|
||||
"host_name": "hiero",
|
||||
"environment": {
|
||||
"HIERO_PLUGIN_PATH": [
|
||||
"{OPENPYPE_REPOS_ROOT}/openpype/hosts/hiero/startup"
|
||||
],
|
||||
"PATH": {
|
||||
"windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}"
|
||||
},
|
||||
"WORKFILES_STARTUP": "0",
|
||||
"TAG_ASSETBUILD_STARTUP": "0",
|
||||
"LOGLEVEL": "DEBUG"
|
||||
"TAG_ASSETBUILD_STARTUP": "0"
|
||||
},
|
||||
"variants": {
|
||||
"13-0": {
|
||||
|
|
@ -510,15 +480,8 @@
|
|||
"icon": "{}/app_icons/hiero.png",
|
||||
"host_name": "hiero",
|
||||
"environment": {
|
||||
"HIERO_PLUGIN_PATH": [
|
||||
"{OPENPYPE_REPOS_ROOT}/openpype/hosts/hiero/startup"
|
||||
],
|
||||
"PATH": {
|
||||
"windows": "C:/Program Files (x86)/QuickTime/QTSystem/;{PATH}"
|
||||
},
|
||||
"WORKFILES_STARTUP": "0",
|
||||
"TAG_ASSETBUILD_STARTUP": "0",
|
||||
"LOGLEVEL": "DEBUG"
|
||||
"TAG_ASSETBUILD_STARTUP": "0"
|
||||
},
|
||||
"variants": {
|
||||
"13-0": {
|
||||
|
|
@ -783,18 +746,7 @@
|
|||
"label": "Houdini",
|
||||
"icon": "{}/app_icons/houdini.png",
|
||||
"host_name": "houdini",
|
||||
"environment": {
|
||||
"HOUDINI_PATH": {
|
||||
"darwin": "{OPENPYPE_REPOS_ROOT}/openpype/hosts/houdini/startup:&",
|
||||
"linux": "{OPENPYPE_REPOS_ROOT}/openpype/hosts/houdini/startup:&",
|
||||
"windows": "{OPENPYPE_REPOS_ROOT}/openpype/hosts/houdini/startup;&"
|
||||
},
|
||||
"HOUDINI_MENU_PATH": {
|
||||
"darwin": "{OPENPYPE_REPOS_ROOT}/openpype/hosts/houdini/startup:&",
|
||||
"linux": "{OPENPYPE_REPOS_ROOT}/openpype/hosts/houdini/startup:&",
|
||||
"windows": "{OPENPYPE_REPOS_ROOT}/openpype/hosts/houdini/startup;&"
|
||||
}
|
||||
},
|
||||
"environment": {},
|
||||
"variants": {
|
||||
"18-5": {
|
||||
"use_python_2": true,
|
||||
|
|
@ -852,14 +804,7 @@
|
|||
"label": "Blender",
|
||||
"icon": "{}/app_icons/blender.png",
|
||||
"host_name": "blender",
|
||||
"environment": {
|
||||
"BLENDER_USER_SCRIPTS": "{OPENPYPE_REPOS_ROOT}/repos/avalon-core/setup/blender",
|
||||
"PYTHONPATH": [
|
||||
"{OPENPYPE_REPOS_ROOT}/repos/avalon-core/setup/blender",
|
||||
"{PYTHONPATH}"
|
||||
],
|
||||
"QT_PREFERRED_BINDING": "PySide2"
|
||||
},
|
||||
"environment": {},
|
||||
"variants": {
|
||||
"2-83": {
|
||||
"use_python_2": false,
|
||||
|
|
@ -940,8 +885,7 @@
|
|||
"icon": "{}/app_icons/harmony.png",
|
||||
"host_name": "harmony",
|
||||
"environment": {
|
||||
"AVALON_HARMONY_WORKFILES_ON_LAUNCH": "1",
|
||||
"LIB_OPENHARMONY_PATH": "{OPENPYPE_REPOS_ROOT}/pype/vendor/OpenHarmony"
|
||||
"AVALON_HARMONY_WORKFILES_ON_LAUNCH": "1"
|
||||
},
|
||||
"variants": {
|
||||
"20": {
|
||||
|
|
@ -985,9 +929,7 @@
|
|||
"label": "TVPaint",
|
||||
"icon": "{}/app_icons/tvpaint.png",
|
||||
"host_name": "tvpaint",
|
||||
"environment": {
|
||||
"OPENPYPE_LOG_NO_COLORS": "True"
|
||||
},
|
||||
"environment": {},
|
||||
"variants": {
|
||||
"animation_11-64bits": {
|
||||
"use_python_2": false,
|
||||
|
|
@ -1034,8 +976,6 @@
|
|||
"host_name": "photoshop",
|
||||
"environment": {
|
||||
"AVALON_PHOTOSHOP_WORKFILES_ON_LAUNCH": "1",
|
||||
"OPENPYPE_LOG_NO_COLORS": "Yes",
|
||||
"WEBSOCKET_URL": "ws://localhost:8099/ws/",
|
||||
"WORKFILES_SAVE_AS": "Yes"
|
||||
},
|
||||
"variants": {
|
||||
|
|
@ -1084,8 +1024,6 @@
|
|||
"host_name": "aftereffects",
|
||||
"environment": {
|
||||
"AVALON_AFTEREFFECTS_WORKFILES_ON_LAUNCH": "1",
|
||||
"OPENPYPE_LOG_NO_COLORS": "Yes",
|
||||
"WEBSOCKET_URL": "ws://localhost:8097/ws/",
|
||||
"WORKFILES_SAVE_AS": "Yes"
|
||||
},
|
||||
"variants": {
|
||||
|
|
@ -1159,10 +1097,7 @@
|
|||
"label": "Unreal Editor",
|
||||
"icon": "{}/app_icons/ue4.png'",
|
||||
"host_name": "unreal",
|
||||
"environment": {
|
||||
"AVALON_UNREAL_PLUGIN": "{OPENPYPE_REPOS_ROOT}/repos/avalon-unreal-integration",
|
||||
"OPENPYPE_LOG_NO_COLORS": "True"
|
||||
},
|
||||
"environment": {},
|
||||
"variants": {
|
||||
"4-26": {
|
||||
"use_python_2": false,
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ from .exceptions import (
|
|||
SchemaError,
|
||||
DefaultsNotDefined,
|
||||
StudioDefaultsNotDefined,
|
||||
BaseInvalidValueType,
|
||||
InvalidValueType,
|
||||
InvalidKeySymbols,
|
||||
SchemaMissingFileInfo,
|
||||
|
|
@ -96,7 +97,7 @@ from .input_entities import (
|
|||
PathInput,
|
||||
RawJsonEntity
|
||||
)
|
||||
|
||||
from .color_entity import ColorEntity
|
||||
from .enum_entity import (
|
||||
BaseEnumEntity,
|
||||
EnumEntity,
|
||||
|
|
@ -115,6 +116,7 @@ from .anatomy_entities import AnatomyEntity
|
|||
__all__ = (
|
||||
"DefaultsNotDefined",
|
||||
"StudioDefaultsNotDefined",
|
||||
"BaseInvalidValueType",
|
||||
"InvalidValueType",
|
||||
"InvalidKeySymbols",
|
||||
"SchemaMissingFileInfo",
|
||||
|
|
@ -146,6 +148,8 @@ __all__ = (
|
|||
"PathInput",
|
||||
"RawJsonEntity",
|
||||
|
||||
"ColorEntity",
|
||||
|
||||
"BaseEnumEntity",
|
||||
"EnumEntity",
|
||||
"AppsEnumEntity",
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ from .lib import (
|
|||
)
|
||||
|
||||
from .exceptions import (
|
||||
BaseInvalidValueType,
|
||||
InvalidValueType,
|
||||
SchemeGroupHierarchyBug,
|
||||
EntitySchemaError
|
||||
|
|
@ -377,7 +378,7 @@ class BaseItemEntity(BaseEntity):
|
|||
|
||||
try:
|
||||
new_value = self.convert_to_valid_type(value)
|
||||
except InvalidValueType:
|
||||
except BaseInvalidValueType:
|
||||
new_value = NOT_SET
|
||||
|
||||
if new_value is not NOT_SET:
|
||||
|
|
@ -846,6 +847,13 @@ class ItemEntity(BaseItemEntity):
|
|||
)
|
||||
raise EntitySchemaError(self, reason)
|
||||
|
||||
if self.is_file and self.file_item is not None:
|
||||
reason = (
|
||||
"Entity has set `is_file` to true but"
|
||||
" it's parent is already marked as file item."
|
||||
)
|
||||
raise EntitySchemaError(self, reason)
|
||||
|
||||
super(ItemEntity, self).schema_validations()
|
||||
|
||||
def create_schema_object(self, *args, **kwargs):
|
||||
|
|
|
|||
54
openpype/settings/entities/color_entity.py
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
from .lib import STRING_TYPE
|
||||
from .input_entities import InputEntity
|
||||
from .exceptions import (
|
||||
BaseInvalidValueType,
|
||||
InvalidValueType
|
||||
)
|
||||
|
||||
|
||||
class ColorEntity(InputEntity):
|
||||
schema_types = ["color"]
|
||||
|
||||
def _item_initalization(self):
|
||||
self.valid_value_types = (list, )
|
||||
self.value_on_not_set = [0, 0, 0, 255]
|
||||
|
||||
def convert_to_valid_type(self, value):
|
||||
"""Conversion to valid type.
|
||||
|
||||
Complexity of entity requires to override BaseEntity implementation.
|
||||
"""
|
||||
# Convertion to valid value type `list`
|
||||
if isinstance(value, (set, tuple)):
|
||||
value = list(value)
|
||||
|
||||
# Skip other validations if is not `list`
|
||||
if not isinstance(value, list):
|
||||
raise InvalidValueType(
|
||||
self.valid_value_types, type(value), self.path
|
||||
)
|
||||
|
||||
# Allow list of len 3 (last aplha is set to max)
|
||||
if len(value) == 3:
|
||||
value.append(255)
|
||||
|
||||
if len(value) != 4:
|
||||
reason = "Color entity expect 4 items in list got {}".format(
|
||||
len(value)
|
||||
)
|
||||
raise BaseInvalidValueType(reason, self.path)
|
||||
|
||||
new_value = []
|
||||
for item in value:
|
||||
if not isinstance(item, int):
|
||||
if isinstance(item, (STRING_TYPE, float)):
|
||||
item = int(item)
|
||||
|
||||
is_valid = isinstance(item, int) and -1 < item < 256
|
||||
if not is_valid:
|
||||
reason = (
|
||||
"Color entity expect 4 integers in range 0-255 got {}"
|
||||
).format(value)
|
||||
raise BaseInvalidValueType(reason, self.path)
|
||||
new_value.append(item)
|
||||
return new_value
|
||||
|
|
@ -15,20 +15,22 @@ class StudioDefaultsNotDefined(Exception):
|
|||
super(StudioDefaultsNotDefined, self).__init__(msg)
|
||||
|
||||
|
||||
class InvalidValueType(Exception):
|
||||
msg_template = "{}"
|
||||
class BaseInvalidValueType(Exception):
|
||||
def __init__(self, reason, path):
|
||||
msg = "Path \"{}\". {}".format(path, reason)
|
||||
self.msg = msg
|
||||
super(BaseInvalidValueType, self).__init__(msg)
|
||||
|
||||
|
||||
class InvalidValueType(BaseInvalidValueType):
|
||||
def __init__(self, valid_types, invalid_type, path):
|
||||
msg = "Path \"{}\". ".format(path)
|
||||
|
||||
joined_types = ", ".join(
|
||||
[str(valid_type) for valid_type in valid_types]
|
||||
)
|
||||
msg += "Got invalid type \"{}\". Expected: {}".format(
|
||||
msg = "Got invalid type \"{}\". Expected: {}".format(
|
||||
invalid_type, joined_types
|
||||
)
|
||||
self.msg = msg
|
||||
super(InvalidValueType, self).__init__(msg)
|
||||
super(InvalidValueType, self).__init__(msg, path)
|
||||
|
||||
|
||||
class RequiredKeyModified(KeyError):
|
||||
|
|
|
|||
|
|
@ -420,6 +420,18 @@
|
|||
}
|
||||
```
|
||||
|
||||
### color
|
||||
- preimplemented entity to store and load color values
|
||||
- entity store and expect list of 4 integers in range 0-255
|
||||
- integers represents rgba [Red, Green, Blue, Alpha]
|
||||
|
||||
```
|
||||
{
|
||||
"type": "color",
|
||||
"key": "bg_color",
|
||||
"label": "Background Color"
|
||||
}
|
||||
```
|
||||
|
||||
## Noninteractive widgets
|
||||
- have nothing to do with data
|
||||
|
|
|
|||
|
|
@ -82,6 +82,10 @@
|
|||
"type": "schema",
|
||||
"name": "schema_project_aftereffects"
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_project_photoshop"
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_project_harmony"
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
"collapsible": true,
|
||||
"key": "publish",
|
||||
"label": "Publish plugins",
|
||||
"is_file": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "dict",
|
||||
|
|
|
|||
|
|
@ -603,7 +603,6 @@
|
|||
"collapsible": true,
|
||||
"key": "publish",
|
||||
"label": "Publish plugins",
|
||||
"is_file": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "dict",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "photoshop",
|
||||
"label": "Photoshop",
|
||||
"is_file": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "create",
|
||||
"label": "Creator plugins",
|
||||
"children": [
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "CreateImage",
|
||||
"label": "Create Image",
|
||||
"children": [
|
||||
{
|
||||
"type": "list",
|
||||
"key": "defaults",
|
||||
"label": "Default Subsets",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "publish",
|
||||
"label": "Publish plugins",
|
||||
"children": [
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "ExtractImage",
|
||||
"label": "Extract Image",
|
||||
"children": [
|
||||
{
|
||||
"type": "label",
|
||||
"label": "Currently only jpg and png are supported"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "formats",
|
||||
"label": "Extract Formats",
|
||||
"object_type": "text"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -11,7 +11,6 @@
|
|||
"key": "create",
|
||||
"label": "Creator plugins",
|
||||
"collapsible_key": true,
|
||||
"is_file": true,
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
|
|
@ -56,7 +55,6 @@
|
|||
"collapsible": true,
|
||||
"key": "publish",
|
||||
"label": "Publish plugins",
|
||||
"is_file": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "dict",
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
"label": "Site Sync (beta testing)",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"is_file": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
|
|
@ -44,7 +43,6 @@
|
|||
"key": "sites",
|
||||
"label": "Sites",
|
||||
"collapsible_key": false,
|
||||
"is_file": true,
|
||||
"object_type":
|
||||
{
|
||||
"type": "dict",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
"collapsible": true,
|
||||
"key": "publish",
|
||||
"label": "Publish plugins",
|
||||
"is_file": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "schema_template",
|
||||
|
|
@ -47,6 +46,72 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "load",
|
||||
"label": "Loader plugins",
|
||||
"children": [
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "LoadImage",
|
||||
"label": "Load Image",
|
||||
"children": [
|
||||
{
|
||||
"key": "defaults",
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "stretch",
|
||||
"label": "Stretch"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "timestretch",
|
||||
"label": "TimeStretch"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "preload",
|
||||
"label": "Preload"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "ImportImage",
|
||||
"label": "Import Image",
|
||||
"children": [
|
||||
{
|
||||
"key": "defaults",
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "stretch",
|
||||
"label": "Stretch"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "timestretch",
|
||||
"label": "TimeStretch"
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "preload",
|
||||
"label": "Preload"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_publish_gui_filter"
|
||||
|
|
|
|||
|
|
@ -228,14 +228,9 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_rgba_color",
|
||||
"template_data": [
|
||||
{
|
||||
"label": "Fill Color",
|
||||
"name": "fill_color"
|
||||
}
|
||||
]
|
||||
"type": "color",
|
||||
"label": "Fill Color",
|
||||
"key": "fill_color"
|
||||
},
|
||||
{
|
||||
"key": "line_thickness",
|
||||
|
|
@ -245,14 +240,9 @@
|
|||
"maximum": 1000
|
||||
},
|
||||
{
|
||||
"type": "schema_template",
|
||||
"name": "template_rgba_color",
|
||||
"template_data": [
|
||||
{
|
||||
"label": "Line Color",
|
||||
"name": "line_color"
|
||||
}
|
||||
]
|
||||
"type": "color",
|
||||
"label": "Line Color",
|
||||
"key": "line_color"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,11 @@
|
|||
"type": "dict",
|
||||
"is_file": true,
|
||||
"children": [
|
||||
{
|
||||
"key": "color",
|
||||
"label": "Color input",
|
||||
"type": "color"
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"key": "schema_template_exaples",
|
||||
|
|
|
|||
|
|
@ -97,7 +97,6 @@
|
|||
"key": "sites",
|
||||
"label": "Sites",
|
||||
"collapsible_key": false,
|
||||
"is_file": true,
|
||||
"object_type":
|
||||
{
|
||||
"type": "dict",
|
||||
|
|
@ -156,8 +155,7 @@
|
|||
},
|
||||
"is_group": true,
|
||||
"key": "templates_mapping",
|
||||
"label": "Templates mapping",
|
||||
"is_file": true
|
||||
"label": "Templates mapping"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ class App(QtWidgets.QWidget):
|
|||
for i, (asset, item) in enumerate(asset_nodes.items()):
|
||||
|
||||
# Label prefix
|
||||
prefix = "({}/{})".format(i+1, len(asset_nodes))
|
||||
prefix = "({}/{})".format(i + 1, len(asset_nodes))
|
||||
|
||||
# Assign the first matching look relevant for this asset
|
||||
# (since assigning multiple to the same nodes makes no sense)
|
||||
|
|
@ -212,18 +212,19 @@ class App(QtWidgets.QWidget):
|
|||
self.echo("{} Assigning {} to {}\t".format(prefix,
|
||||
subset_name,
|
||||
asset))
|
||||
nodes = item["nodes"]
|
||||
|
||||
self.echo("Getting vray proxy nodes ...")
|
||||
vray_proxies = set(cmds.ls(type="VRayProxy"))
|
||||
nodes = set(item["nodes"]).difference(vray_proxies)
|
||||
if cmds.pluginInfo('vrayformaya', query=True, loaded=True):
|
||||
self.echo("Getting vray proxy nodes ...")
|
||||
vray_proxies = set(cmds.ls(type="VRayProxy"))
|
||||
nodes = list(set(item["nodes"]).difference(vray_proxies))
|
||||
if vray_proxies:
|
||||
for vp in vray_proxies:
|
||||
vrayproxy_assign_look(vp, subset_name)
|
||||
|
||||
# Assign look
|
||||
# Assign look
|
||||
if nodes:
|
||||
assign_look_by_version([nodes], version_id=version["_id"])
|
||||
|
||||
if vray_proxies:
|
||||
for vp in vray_proxies:
|
||||
vrayproxy_assign_look(vp, subset_name)
|
||||
assign_look_by_version(nodes, version_id=version["_id"])
|
||||
|
||||
end = time.time()
|
||||
|
||||
|
|
|
|||
10
openpype/tools/project_manager/__init__.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from .project_manager import (
|
||||
ProjectManagerWindow,
|
||||
main
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
"ProjectManagerWindow",
|
||||
"main"
|
||||
)
|
||||
5
openpype/tools/project_manager/__main__.py
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
from project_manager import main
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
50
openpype/tools/project_manager/project_manager/__init__.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
__all__ = (
|
||||
"IDENTIFIER_ROLE",
|
||||
|
||||
"HierarchyView",
|
||||
|
||||
"ProjectModel",
|
||||
"CreateProjectDialog",
|
||||
|
||||
"HierarchyModel",
|
||||
"HierarchySelectionModel",
|
||||
"BaseItem",
|
||||
"RootItem",
|
||||
"ProjectItem",
|
||||
"AssetItem",
|
||||
"TaskItem",
|
||||
|
||||
"ProjectManagerWindow",
|
||||
"main"
|
||||
)
|
||||
|
||||
|
||||
from .constants import (
|
||||
IDENTIFIER_ROLE
|
||||
)
|
||||
from .widgets import CreateProjectDialog
|
||||
from .view import HierarchyView
|
||||
from .model import (
|
||||
ProjectModel,
|
||||
|
||||
HierarchyModel,
|
||||
HierarchySelectionModel,
|
||||
BaseItem,
|
||||
RootItem,
|
||||
ProjectItem,
|
||||
AssetItem,
|
||||
TaskItem
|
||||
)
|
||||
from .window import ProjectManagerWindow
|
||||
|
||||
|
||||
def main():
|
||||
import sys
|
||||
from Qt import QtWidgets
|
||||
|
||||
app = QtWidgets.QApplication([])
|
||||
|
||||
window = ProjectManagerWindow()
|
||||
window.show()
|
||||
|
||||
sys.exit(app.exec_())
|
||||
13
openpype/tools/project_manager/project_manager/constants.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import re
|
||||
from Qt import QtCore
|
||||
|
||||
|
||||
IDENTIFIER_ROLE = QtCore.Qt.UserRole + 1
|
||||
DUPLICATED_ROLE = QtCore.Qt.UserRole + 2
|
||||
HIERARCHY_CHANGE_ABLE_ROLE = QtCore.Qt.UserRole + 3
|
||||
REMOVED_ROLE = QtCore.Qt.UserRole + 4
|
||||
ITEM_TYPE_ROLE = QtCore.Qt.UserRole + 5
|
||||
EDITOR_OPENED_ROLE = QtCore.Qt.UserRole + 6
|
||||
|
||||
NAME_ALLOWED_SYMBOLS = "a-zA-Z0-9_"
|
||||
NAME_REGEX = re.compile("^[" + NAME_ALLOWED_SYMBOLS + "]*$")
|
||||
159
openpype/tools/project_manager/project_manager/delegates.py
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
from Qt import QtWidgets, QtCore
|
||||
|
||||
from .widgets import (
|
||||
NameTextEdit,
|
||||
FilterComboBox
|
||||
)
|
||||
from .multiselection_combobox import MultiSelectionComboBox
|
||||
|
||||
|
||||
class ResizeEditorDelegate(QtWidgets.QStyledItemDelegate):
|
||||
@staticmethod
|
||||
def _q_smart_min_size(editor):
|
||||
min_size_hint = editor.minimumSizeHint()
|
||||
size_policy = editor.sizePolicy()
|
||||
width = 0
|
||||
height = 0
|
||||
if size_policy.horizontalPolicy() != QtWidgets.QSizePolicy.Ignored:
|
||||
if (
|
||||
size_policy.horizontalPolicy()
|
||||
& QtWidgets.QSizePolicy.ShrinkFlag
|
||||
):
|
||||
width = min_size_hint.width()
|
||||
else:
|
||||
width = max(
|
||||
editor.sizeHint().width(),
|
||||
min_size_hint.width()
|
||||
)
|
||||
|
||||
if size_policy.verticalPolicy() != QtWidgets.QSizePolicy.Ignored:
|
||||
if size_policy.verticalPolicy() & QtWidgets.QSizePolicy.ShrinkFlag:
|
||||
height = min_size_hint.height()
|
||||
else:
|
||||
height = max(
|
||||
editor.sizeHint().height(),
|
||||
min_size_hint.height()
|
||||
)
|
||||
|
||||
output = QtCore.QSize(width, height).boundedTo(editor.maximumSize())
|
||||
min_size = editor.minimumSize()
|
||||
if min_size.width() > 0:
|
||||
output.setWidth(min_size.width())
|
||||
if min_size.height() > 0:
|
||||
output.setHeight(min_size.height())
|
||||
|
||||
return output.expandedTo(QtCore.QSize(0, 0))
|
||||
|
||||
def updateEditorGeometry(self, editor, option, index):
|
||||
self.initStyleOption(option, index)
|
||||
|
||||
option.showDecorationSelected = editor.style().styleHint(
|
||||
QtWidgets.QStyle.SH_ItemView_ShowDecorationSelected, None, editor
|
||||
)
|
||||
|
||||
widget = option.widget
|
||||
|
||||
style = widget.style() if widget else QtWidgets.QApplication.style()
|
||||
geo = style.subElementRect(
|
||||
QtWidgets.QStyle.SE_ItemViewItemText, option, widget
|
||||
)
|
||||
delta = self._q_smart_min_size(editor).width() - geo.width()
|
||||
if delta > 0:
|
||||
if editor.layoutDirection() == QtCore.Qt.RightToLeft:
|
||||
geo.adjust(-delta, 0, 0, 0)
|
||||
else:
|
||||
geo.adjust(0, 0, delta, 0)
|
||||
editor.setGeometry(geo)
|
||||
|
||||
|
||||
class NumberDelegate(QtWidgets.QStyledItemDelegate):
|
||||
def __init__(self, minimum, maximum, decimals, *args, **kwargs):
|
||||
super(NumberDelegate, self).__init__(*args, **kwargs)
|
||||
self.minimum = minimum
|
||||
self.maximum = maximum
|
||||
self.decimals = decimals
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
if self.decimals > 0:
|
||||
editor = QtWidgets.QDoubleSpinBox(parent)
|
||||
else:
|
||||
editor = QtWidgets.QSpinBox(parent)
|
||||
|
||||
editor.setObjectName("NumberEditor")
|
||||
editor.setMinimum(self.minimum)
|
||||
editor.setMaximum(self.maximum)
|
||||
editor.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
|
||||
value = index.data(QtCore.Qt.EditRole)
|
||||
if value is not None:
|
||||
try:
|
||||
if isinstance(value, str):
|
||||
value = float(value)
|
||||
editor.setValue(value)
|
||||
|
||||
except Exception:
|
||||
print("Couldn't set invalid value \"{}\"".format(str(value)))
|
||||
|
||||
return editor
|
||||
|
||||
|
||||
class NameDelegate(QtWidgets.QStyledItemDelegate):
|
||||
def createEditor(self, parent, option, index):
|
||||
editor = NameTextEdit(parent)
|
||||
editor.setObjectName("NameEditor")
|
||||
value = index.data(QtCore.Qt.EditRole)
|
||||
if value is not None:
|
||||
editor.setText(str(value))
|
||||
return editor
|
||||
|
||||
|
||||
class TypeDelegate(QtWidgets.QStyledItemDelegate):
|
||||
def __init__(self, project_doc_cache, *args, **kwargs):
|
||||
self._project_doc_cache = project_doc_cache
|
||||
super(TypeDelegate, self).__init__(*args, **kwargs)
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
editor = FilterComboBox(parent)
|
||||
editor.setObjectName("TypeEditor")
|
||||
editor.style().polish(editor)
|
||||
if not self._project_doc_cache.project_doc:
|
||||
return editor
|
||||
|
||||
task_type_defs = self._project_doc_cache.project_doc["config"]["tasks"]
|
||||
editor.addItems(list(task_type_defs.keys()))
|
||||
|
||||
return editor
|
||||
|
||||
def setEditorData(self, editor, index):
|
||||
value = index.data(QtCore.Qt.EditRole)
|
||||
index = editor.findText(value)
|
||||
if index >= 0:
|
||||
editor.setCurrentIndex(index)
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
editor.value_cleanup()
|
||||
super(TypeDelegate, self).setModelData(editor, model, index)
|
||||
|
||||
|
||||
class ToolsDelegate(QtWidgets.QStyledItemDelegate):
|
||||
def __init__(self, tools_cache, *args, **kwargs):
|
||||
self._tools_cache = tools_cache
|
||||
super(ToolsDelegate, self).__init__(*args, **kwargs)
|
||||
|
||||
def createEditor(self, parent, option, index):
|
||||
editor = MultiSelectionComboBox(parent)
|
||||
editor.setObjectName("ToolEditor")
|
||||
if not self._tools_cache.tools_data:
|
||||
return editor
|
||||
|
||||
for key, label in self._tools_cache.tools_data:
|
||||
editor.addItem(label, key)
|
||||
|
||||
return editor
|
||||
|
||||
def setEditorData(self, editor, index):
|
||||
value = index.data(QtCore.Qt.EditRole)
|
||||
editor.set_value(value)
|
||||
|
||||
def setModelData(self, editor, model, index):
|
||||
model.setData(index, editor.value(), QtCore.Qt.EditRole)
|
||||
2040
openpype/tools/project_manager/project_manager/model.py
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
from Qt import QtCore, QtGui, QtWidgets
|
||||
|
||||
|
||||
class ComboItemDelegate(QtWidgets.QStyledItemDelegate):
|
||||
"""
|
||||
Helper styled delegate (mostly based on existing private Qt's
|
||||
delegate used by the QtWidgets.QComboBox). Used to style the popup like a
|
||||
list view (e.g windows style).
|
||||
"""
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
option = QtWidgets.QStyleOptionViewItem(option)
|
||||
option.showDecorationSelected = True
|
||||
|
||||
# option.state &= (
|
||||
# ~QtWidgets.QStyle.State_HasFocus
|
||||
# & ~QtWidgets.QStyle.State_MouseOver
|
||||
# )
|
||||
super(ComboItemDelegate, self).paint(painter, option, index)
|
||||
|
||||
|
||||
class MultiSelectionComboBox(QtWidgets.QComboBox):
|
||||
value_changed = QtCore.Signal()
|
||||
ignored_keys = {
|
||||
QtCore.Qt.Key_Up,
|
||||
QtCore.Qt.Key_Down,
|
||||
QtCore.Qt.Key_PageDown,
|
||||
QtCore.Qt.Key_PageUp,
|
||||
QtCore.Qt.Key_Home,
|
||||
QtCore.Qt.Key_End
|
||||
}
|
||||
|
||||
def __init__(self, parent=None, **kwargs):
|
||||
super(MultiSelectionComboBox, self).__init__(parent=parent, **kwargs)
|
||||
self.setObjectName("MultiSelectionComboBox")
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
||||
self._popup_is_shown = False
|
||||
self._block_mouse_release_timer = QtCore.QTimer(self, singleShot=True)
|
||||
self._initial_mouse_pos = None
|
||||
self._delegate = ComboItemDelegate(self)
|
||||
self.setItemDelegate(self._delegate)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
"""Reimplemented."""
|
||||
self._popup_is_shown = False
|
||||
super(MultiSelectionComboBox, self).mousePressEvent(event)
|
||||
if self._popup_is_shown:
|
||||
self._initial_mouse_pos = self.mapToGlobal(event.pos())
|
||||
self._block_mouse_release_timer.start(
|
||||
QtWidgets.QApplication.doubleClickInterval()
|
||||
)
|
||||
|
||||
def showPopup(self):
|
||||
"""Reimplemented."""
|
||||
super(MultiSelectionComboBox, self).showPopup()
|
||||
view = self.view()
|
||||
view.installEventFilter(self)
|
||||
view.viewport().installEventFilter(self)
|
||||
self._popup_is_shown = True
|
||||
|
||||
def hidePopup(self):
|
||||
"""Reimplemented."""
|
||||
self.view().removeEventFilter(self)
|
||||
self.view().viewport().removeEventFilter(self)
|
||||
self._popup_is_shown = False
|
||||
self._initial_mouse_pos = None
|
||||
super(MultiSelectionComboBox, self).hidePopup()
|
||||
self.view().clearFocus()
|
||||
|
||||
def _event_popup_shown(self, obj, event):
|
||||
if not self._popup_is_shown:
|
||||
return
|
||||
|
||||
current_index = self.view().currentIndex()
|
||||
model = self.model()
|
||||
|
||||
if event.type() == QtCore.QEvent.MouseMove:
|
||||
if (
|
||||
self.view().isVisible()
|
||||
and self._initial_mouse_pos is not None
|
||||
and self._block_mouse_release_timer.isActive()
|
||||
):
|
||||
diff = obj.mapToGlobal(event.pos()) - self._initial_mouse_pos
|
||||
if diff.manhattanLength() > 9:
|
||||
self._block_mouse_release_timer.stop()
|
||||
return
|
||||
|
||||
index_flags = current_index.flags()
|
||||
state = current_index.data(QtCore.Qt.CheckStateRole)
|
||||
new_state = None
|
||||
|
||||
if event.type() == QtCore.QEvent.MouseButtonRelease:
|
||||
if (
|
||||
self._block_mouse_release_timer.isActive()
|
||||
or not current_index.isValid()
|
||||
or not self.view().isVisible()
|
||||
or not self.view().rect().contains(event.pos())
|
||||
or not index_flags & QtCore.Qt.ItemIsSelectable
|
||||
or not index_flags & QtCore.Qt.ItemIsEnabled
|
||||
or not index_flags & QtCore.Qt.ItemIsUserCheckable
|
||||
):
|
||||
return
|
||||
|
||||
if state == QtCore.Qt.Unchecked:
|
||||
new_state = QtCore.Qt.Checked
|
||||
else:
|
||||
new_state = QtCore.Qt.Unchecked
|
||||
|
||||
elif event.type() == QtCore.QEvent.KeyPress:
|
||||
# TODO: handle QtCore.Qt.Key_Enter, Key_Return?
|
||||
if event.key() == QtCore.Qt.Key_Space:
|
||||
# toogle the current items check state
|
||||
if (
|
||||
index_flags & QtCore.Qt.ItemIsUserCheckable
|
||||
and index_flags & QtCore.Qt.ItemIsTristate
|
||||
):
|
||||
new_state = QtCore.Qt.CheckState((int(state) + 1) % 3)
|
||||
|
||||
elif index_flags & QtCore.Qt.ItemIsUserCheckable:
|
||||
if state != QtCore.Qt.Checked:
|
||||
new_state = QtCore.Qt.Checked
|
||||
else:
|
||||
new_state = QtCore.Qt.Unchecked
|
||||
|
||||
if new_state is not None:
|
||||
model.setData(current_index, new_state, QtCore.Qt.CheckStateRole)
|
||||
self.view().update(current_index)
|
||||
self.value_changed.emit()
|
||||
return True
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
"""Reimplemented."""
|
||||
result = self._event_popup_shown(obj, event)
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
return super(MultiSelectionComboBox, self).eventFilter(obj, event)
|
||||
|
||||
def addItem(self, *args, **kwargs):
|
||||
idx = self.count()
|
||||
super(MultiSelectionComboBox, self).addItem(*args, **kwargs)
|
||||
self.model().item(idx).setCheckable(True)
|
||||
|
||||
def paintEvent(self, event):
|
||||
"""Reimplemented."""
|
||||
painter = QtWidgets.QStylePainter(self)
|
||||
option = QtWidgets.QStyleOptionComboBox()
|
||||
self.initStyleOption(option)
|
||||
painter.drawComplexControl(QtWidgets.QStyle.CC_ComboBox, option)
|
||||
|
||||
# draw the icon and text
|
||||
items = self.checked_items_text()
|
||||
if not items:
|
||||
return
|
||||
|
||||
text_rect = self.style().subControlRect(
|
||||
QtWidgets.QStyle.CC_ComboBox,
|
||||
option,
|
||||
QtWidgets.QStyle.SC_ComboBoxEditField
|
||||
)
|
||||
text = ", ".join(items)
|
||||
new_text = self.fontMetrics().elidedText(
|
||||
text, QtCore.Qt.ElideRight, text_rect.width()
|
||||
)
|
||||
painter.drawText(
|
||||
text_rect,
|
||||
QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter,
|
||||
new_text
|
||||
)
|
||||
|
||||
def setItemCheckState(self, index, state):
|
||||
self.setItemData(index, state, QtCore.Qt.CheckStateRole)
|
||||
|
||||
def set_value(self, values):
|
||||
for idx in range(self.count()):
|
||||
value = self.itemData(idx, role=QtCore.Qt.UserRole)
|
||||
if value in values:
|
||||
check_state = QtCore.Qt.Checked
|
||||
else:
|
||||
check_state = QtCore.Qt.Unchecked
|
||||
self.setItemData(idx, check_state, QtCore.Qt.CheckStateRole)
|
||||
|
||||
def value(self):
|
||||
items = list()
|
||||
for idx in range(self.count()):
|
||||
state = self.itemData(idx, role=QtCore.Qt.CheckStateRole)
|
||||
if state == QtCore.Qt.Checked:
|
||||
items.append(
|
||||
self.itemData(idx, role=QtCore.Qt.UserRole)
|
||||
)
|
||||
return items
|
||||
|
||||
def checked_items_text(self):
|
||||
items = list()
|
||||
for idx in range(self.count()):
|
||||
state = self.itemData(idx, role=QtCore.Qt.CheckStateRole)
|
||||
if state == QtCore.Qt.Checked:
|
||||
items.append(self.itemText(idx))
|
||||
return items
|
||||
|
||||
def wheelEvent(self, event):
|
||||
event.ignore()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if (
|
||||
event.key() == QtCore.Qt.Key_Down
|
||||
and event.modifiers() & QtCore.Qt.AltModifier
|
||||
):
|
||||
return self.showPopup()
|
||||
|
||||
if event.key() in self.ignored_keys:
|
||||
return event.ignore()
|
||||
|
||||
return super(MultiSelectionComboBox, self).keyPressEvent(event)
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
import os
|
||||
from openpype import resources
|
||||
from avalon.vendor import qtawesome
|
||||
|
||||
|
||||
class ResourceCache:
|
||||
colors = {
|
||||
"standard": "#333333",
|
||||
"new": "#2d9a4c",
|
||||
"warning": "#c83232"
|
||||
}
|
||||
icons = None
|
||||
|
||||
@classmethod
|
||||
def get_icon(cls, *keys):
|
||||
output = cls.get_icons()
|
||||
for key in keys:
|
||||
output = output[key]
|
||||
return output
|
||||
|
||||
@classmethod
|
||||
def get_icons(cls):
|
||||
if cls.icons is None:
|
||||
cls.icons = {
|
||||
"asset": {
|
||||
"default": qtawesome.icon(
|
||||
"fa.folder",
|
||||
color=cls.colors["standard"]
|
||||
),
|
||||
"new": qtawesome.icon(
|
||||
"fa.folder",
|
||||
color=cls.colors["new"]
|
||||
),
|
||||
"invalid": qtawesome.icon(
|
||||
"fa.exclamation-triangle",
|
||||
color=cls.colors["warning"]
|
||||
),
|
||||
"removed": qtawesome.icon(
|
||||
"fa.trash",
|
||||
color=cls.colors["warning"]
|
||||
)
|
||||
},
|
||||
"task": {
|
||||
"default": qtawesome.icon(
|
||||
"fa.check-circle-o",
|
||||
color=cls.colors["standard"]
|
||||
),
|
||||
"new": qtawesome.icon(
|
||||
"fa.check-circle",
|
||||
color=cls.colors["new"]
|
||||
),
|
||||
"invalid": qtawesome.icon(
|
||||
"fa.exclamation-circle",
|
||||
color=cls.colors["warning"]
|
||||
),
|
||||
"removed": qtawesome.icon(
|
||||
"fa.trash",
|
||||
color=cls.colors["warning"]
|
||||
)
|
||||
},
|
||||
"refresh": qtawesome.icon(
|
||||
"fa.refresh",
|
||||
color=cls.colors["standard"]
|
||||
)
|
||||
}
|
||||
return cls.icons
|
||||
|
||||
@classmethod
|
||||
def get_color(cls, color_name):
|
||||
return cls.colors[color_name]
|
||||
|
||||
@classmethod
|
||||
def style_fill_data(cls):
|
||||
output = {}
|
||||
for color_name, color_value in cls.colors.items():
|
||||
key = "color:{}".format(color_name)
|
||||
output[key] = color_value
|
||||
return output
|
||||
|
||||
|
||||
def load_stylesheet():
|
||||
from . import qrc_resources
|
||||
|
||||
qrc_resources.qInitResources()
|
||||
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
style_path = os.path.join(current_dir, "style.css")
|
||||
with open(style_path, "r") as style_file:
|
||||
stylesheet = style_file.read()
|
||||
|
||||
for key, value in ResourceCache.style_fill_data().items():
|
||||
replacement_key = "{" + key + "}"
|
||||
stylesheet = stylesheet.replace(replacement_key, value)
|
||||
return stylesheet
|
||||
|
||||
|
||||
def app_icon_path():
|
||||
return resources.pype_icon_filepath()
|
||||
|
After Width: | Height: | Size: 166 B |
|
After Width: | Height: | Size: 165 B |
|
|
@ -0,0 +1,105 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Resource object code
|
||||
#
|
||||
# Created by: The Resource Compiler for PyQt5 (Qt v5.15.2)
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PyQt5 import QtCore
|
||||
|
||||
|
||||
qt_resource_data = b"\
|
||||
\x00\x00\x00\xa5\
|
||||
\x89\
|
||||
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
|
||||
\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\
|
||||
\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\
|
||||
\x02\x62\x4b\x47\x44\x00\x9c\x53\x34\xfc\x5d\x00\x00\x00\x09\x70\
|
||||
\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\
|
||||
\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x0b\x02\x04\x6d\
|
||||
\x98\x1b\x69\x00\x00\x00\x29\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\
|
||||
\x00\x8c\x0c\x0c\xff\xcf\xa3\x08\x18\x32\x32\x30\x20\x0b\x32\x1a\
|
||||
\x32\x30\x30\x42\x98\x10\x41\x46\x43\x14\x13\x50\xb5\xa3\x01\x00\
|
||||
\xd6\x10\x07\xd2\x2f\x48\xdf\x4a\x00\x00\x00\x00\x49\x45\x4e\x44\
|
||||
\xae\x42\x60\x82\
|
||||
\x00\x00\x00\xa6\
|
||||
\x89\
|
||||
\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52\x00\
|
||||
\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce\x7c\x4e\
|
||||
\x00\x00\x00\x01\x73\x52\x47\x42\x00\xae\xce\x1c\xe9\x00\x00\x00\
|
||||
\x02\x62\x4b\x47\x44\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09\x70\
|
||||
\x48\x59\x73\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\
|
||||
\x00\x00\x00\x07\x74\x49\x4d\x45\x07\xdc\x08\x17\x08\x15\x3b\xdc\
|
||||
\x3b\x0c\x9b\x00\x00\x00\x2a\x49\x44\x41\x54\x08\xd7\x63\x60\xc0\
|
||||
\x00\x8c\x0c\x0c\x73\x3e\x20\x0b\xa4\x08\x30\x32\x30\x20\x0b\xa6\
|
||||
\x08\x30\x30\x30\x42\x98\x10\xc1\x14\x01\x14\x13\x50\xb5\xa3\x01\
|
||||
\x00\xc6\xb9\x07\x90\x5d\x66\x1f\x83\x00\x00\x00\x00\x49\x45\x4e\
|
||||
\x44\xae\x42\x60\x82\
|
||||
"
|
||||
|
||||
|
||||
qt_resource_name = b"\
|
||||
\x00\x08\
|
||||
\x06\xc5\x8e\xa5\
|
||||
\x00\x6f\
|
||||
\x00\x70\x00\x65\x00\x6e\x00\x70\x00\x79\x00\x70\x00\x65\
|
||||
\x00\x06\
|
||||
\x07\x03\x7d\xc3\
|
||||
\x00\x69\
|
||||
\x00\x6d\x00\x61\x00\x67\x00\x65\x00\x73\
|
||||
\x00\x12\
|
||||
\x01\x2e\x03\x27\
|
||||
\x00\x63\
|
||||
\x00\x6f\x00\x6d\x00\x62\x00\x6f\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x2e\x00\x70\x00\x6e\
|
||||
\x00\x67\
|
||||
\x00\x1b\
|
||||
\x03\x5a\x32\x27\
|
||||
\x00\x63\
|
||||
\x00\x6f\x00\x6d\x00\x62\x00\x6f\x00\x62\x00\x6f\x00\x78\x00\x5f\x00\x61\x00\x72\x00\x72\x00\x6f\x00\x77\x00\x5f\x00\x64\x00\x69\
|
||||
\x00\x73\x00\x61\x00\x62\x00\x6c\x00\x65\x00\x64\x00\x2e\x00\x70\x00\x6e\x00\x67\
|
||||
"
|
||||
|
||||
|
||||
qt_resource_struct_v1 = b"\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
|
||||
\x00\x00\x00\x16\x00\x02\x00\x00\x00\x02\x00\x00\x00\x03\
|
||||
\x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x52\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa9\
|
||||
"
|
||||
|
||||
|
||||
qt_resource_struct_v2 = b"\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x16\x00\x02\x00\x00\x00\x02\x00\x00\x00\x03\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x28\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\x00\x00\x01\x76\x41\x9d\xa2\x35\
|
||||
\x00\x00\x00\x52\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa9\
|
||||
\x00\x00\x01\x76\x41\x9d\xa2\x35\
|
||||
"
|
||||
|
||||
|
||||
qt_version = [int(v) for v in QtCore.qVersion().split('.')]
|
||||
if qt_version < [5, 8, 0]:
|
||||
rcc_version = 1
|
||||
qt_resource_struct = qt_resource_struct_v1
|
||||
else:
|
||||
rcc_version = 2
|
||||
qt_resource_struct = qt_resource_struct_v2
|
||||
|
||||
|
||||
def qInitResources():
|
||||
QtCore.qRegisterResourceData(
|
||||
rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data
|
||||
)
|
||||
|
||||
|
||||
def qCleanupResources():
|
||||
QtCore.qUnregisterResourceData(
|
||||
rcc_version, qt_resource_struct, qt_resource_name, qt_resource_data
|
||||
)
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
# Resource object code (Python 3)
|
||||
# Created by: object code
|
||||
# Created by: The Resource Compiler for Qt version 5.15.2
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
from PySide2 import QtCore
|
||||
|
||||
|
||||
qt_resource_data = b"\
|
||||
\x00\x00\x00\xa5\
|
||||
\x89\
|
||||
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
||||
\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce|N\
|
||||
\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
|
||||
\x02bKGD\x00\x9cS4\xfc]\x00\x00\x00\x09p\
|
||||
HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\
|
||||
\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x0b\x02\x04m\
|
||||
\x98\x1bi\x00\x00\x00)IDAT\x08\xd7c`\xc0\
|
||||
\x00\x8c\x0c\x0c\xff\xcf\xa3\x08\x18220 \x0b2\x1a\
|
||||
200B\x98\x10AFC\x14\x13P\xb5\xa3\x01\x00\
|
||||
\xd6\x10\x07\xd2/H\xdfJ\x00\x00\x00\x00IEND\
|
||||
\xaeB`\x82\
|
||||
\x00\x00\x00\xa6\
|
||||
\x89\
|
||||
PNG\x0d\x0a\x1a\x0a\x00\x00\x00\x0dIHDR\x00\
|
||||
\x00\x00\x09\x00\x00\x00\x06\x08\x04\x00\x00\x00\xbb\xce|N\
|
||||
\x00\x00\x00\x01sRGB\x00\xae\xce\x1c\xe9\x00\x00\x00\
|
||||
\x02bKGD\x00\xff\x87\x8f\xcc\xbf\x00\x00\x00\x09p\
|
||||
HYs\x00\x00\x0b\x13\x00\x00\x0b\x13\x01\x00\x9a\x9c\x18\
|
||||
\x00\x00\x00\x07tIME\x07\xdc\x08\x17\x08\x15;\xdc\
|
||||
;\x0c\x9b\x00\x00\x00*IDAT\x08\xd7c`\xc0\
|
||||
\x00\x8c\x0c\x0cs> \x0b\xa4\x08020 \x0b\xa6\
|
||||
\x08000B\x98\x10\xc1\x14\x01\x14\x13P\xb5\xa3\x01\
|
||||
\x00\xc6\xb9\x07\x90]f\x1f\x83\x00\x00\x00\x00IEN\
|
||||
D\xaeB`\x82\
|
||||
"
|
||||
|
||||
|
||||
qt_resource_name = b"\
|
||||
\x00\x08\
|
||||
\x06\xc5\x8e\xa5\
|
||||
\x00o\
|
||||
\x00p\x00e\x00n\x00p\x00y\x00p\x00e\
|
||||
\x00\x06\
|
||||
\x07\x03}\xc3\
|
||||
\x00i\
|
||||
\x00m\x00a\x00g\x00e\x00s\
|
||||
\x00\x12\
|
||||
\x01.\x03'\
|
||||
\x00c\
|
||||
\x00o\x00m\x00b\x00o\x00b\x00o\x00x\x00_\x00a\x00r\x00r\x00o\x00w\x00.\x00p\x00n\
|
||||
\x00g\
|
||||
\x00\x1b\
|
||||
\x03Z2'\
|
||||
\x00c\
|
||||
\x00o\x00m\x00b\x00o\x00b\x00o\x00x\x00_\x00a\x00r\x00r\x00o\x00w\x00_\x00d\x00i\
|
||||
\x00s\x00a\x00b\x00l\x00e\x00d\x00.\x00p\x00n\x00g\
|
||||
"
|
||||
|
||||
|
||||
qt_resource_struct = b"\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00\x16\x00\x02\x00\x00\x00\x02\x00\x00\x00\x03\
|
||||
\x00\x00\x00\x00\x00\x00\x00\x00\
|
||||
\x00\x00\x00(\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\
|
||||
\x00\x00\x01vA\x9d\xa25\
|
||||
\x00\x00\x00R\x00\x00\x00\x00\x00\x01\x00\x00\x00\xa9\
|
||||
\x00\x00\x01vA\x9d\xa25\
|
||||
"
|
||||
|
||||
|
||||
def qInitResources():
|
||||
QtCore.qRegisterResourceData(
|
||||
0x03, qt_resource_struct, qt_resource_name, qt_resource_data
|
||||
)
|
||||
|
||||
|
||||
def qCleanupResources():
|
||||
QtCore.qUnregisterResourceData(
|
||||
0x03, qt_resource_struct, qt_resource_name, qt_resource_data
|
||||
)
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import Qt
|
||||
|
||||
|
||||
initialized = False
|
||||
resources = None
|
||||
if Qt.__binding__ == "PySide2":
|
||||
from . import pyside2_resources as resources
|
||||
elif Qt.__binding__ == "PyQt5":
|
||||
from . import pyqt5_resources as resources
|
||||
|
||||
|
||||
def qInitResources():
|
||||
global resources
|
||||
global initialized
|
||||
if resources is not None and not initialized:
|
||||
initialized = True
|
||||
resources.qInitResources()
|
||||
|
||||
|
||||
def qCleanupResources():
|
||||
global resources
|
||||
global initialized
|
||||
if resources is not None:
|
||||
initialized = False
|
||||
resources.qCleanupResources()
|
||||
|
||||
|
||||
__all__ = (
|
||||
"resources",
|
||||
"qInitResources",
|
||||
"qCleanupResources"
|
||||
)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
<RCC>
|
||||
<qresource prefix="/openpype">
|
||||
<file>images/combobox_arrow.png</file>
|
||||
<file>images/combobox_arrow_disabled.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
QTreeView::item {
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
padding-right: 3px;
|
||||
}
|
||||
|
||||
|
||||
QTreeView::item:selected, QTreeView::item:selected:!active {
|
||||
background: rgba(0, 122, 204, 127);
|
||||
color: black;
|
||||
}
|
||||
|
||||
#RefreshBtn {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
#TypeEditor, #ToolEditor, #NameEditor, #NumberEditor {
|
||||
background: transparent;
|
||||
border: 1px solid #005c99;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
643
openpype/tools/project_manager/project_manager/view.py
Normal file
|
|
@ -0,0 +1,643 @@
|
|||
import collections
|
||||
from queue import Queue
|
||||
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
from .delegates import (
|
||||
NumberDelegate,
|
||||
NameDelegate,
|
||||
TypeDelegate,
|
||||
ToolsDelegate
|
||||
)
|
||||
|
||||
from openpype.lib import ApplicationManager
|
||||
from .constants import (
|
||||
REMOVED_ROLE,
|
||||
IDENTIFIER_ROLE,
|
||||
ITEM_TYPE_ROLE,
|
||||
HIERARCHY_CHANGE_ABLE_ROLE,
|
||||
EDITOR_OPENED_ROLE
|
||||
)
|
||||
|
||||
|
||||
class NameDef:
|
||||
pass
|
||||
|
||||
|
||||
class NumberDef:
|
||||
def __init__(self, minimum=None, maximum=None, decimals=None):
|
||||
self.minimum = 0 if minimum is None else minimum
|
||||
self.maximum = 999999 if maximum is None else maximum
|
||||
self.decimals = 0 if decimals is None else decimals
|
||||
|
||||
|
||||
class TypeDef:
|
||||
pass
|
||||
|
||||
|
||||
class ToolsDef:
|
||||
pass
|
||||
|
||||
|
||||
class ProjectDocCache:
|
||||
def __init__(self, dbcon):
|
||||
self.dbcon = dbcon
|
||||
self.project_doc = None
|
||||
|
||||
def set_project(self, project_name):
|
||||
self.project_doc = None
|
||||
|
||||
if not project_name:
|
||||
return
|
||||
|
||||
self.project_doc = self.dbcon.database[project_name].find_one(
|
||||
{"type": "project"}
|
||||
)
|
||||
|
||||
|
||||
class ToolsCache:
|
||||
def __init__(self):
|
||||
self.tools_data = []
|
||||
|
||||
def refresh(self):
|
||||
app_manager = ApplicationManager()
|
||||
tools_data = []
|
||||
for tool_name, tool in app_manager.tools.items():
|
||||
tools_data.append(
|
||||
(tool_name, tool.label)
|
||||
)
|
||||
self.tools_data = tools_data
|
||||
|
||||
|
||||
class HierarchyView(QtWidgets.QTreeView):
|
||||
"""A tree view that deselects on clicking on an empty area in the view"""
|
||||
column_delegate_defs = {
|
||||
"name": NameDef(),
|
||||
"type": TypeDef(),
|
||||
"frameStart": NumberDef(1),
|
||||
"frameEnd": NumberDef(1),
|
||||
"fps": NumberDef(1, decimals=2),
|
||||
"resolutionWidth": NumberDef(0),
|
||||
"resolutionHeight": NumberDef(0),
|
||||
"handleStart": NumberDef(0),
|
||||
"handleEnd": NumberDef(0),
|
||||
"clipIn": NumberDef(1),
|
||||
"clipOut": NumberDef(1),
|
||||
"pixelAspect": NumberDef(0, decimals=2),
|
||||
"tools_env": ToolsDef()
|
||||
}
|
||||
|
||||
columns_sizes = {
|
||||
"default": {
|
||||
"stretch": QtWidgets.QHeaderView.ResizeToContents
|
||||
},
|
||||
"name": {
|
||||
"stretch": QtWidgets.QHeaderView.Stretch
|
||||
},
|
||||
"type": {
|
||||
"stretch": QtWidgets.QHeaderView.Interactive,
|
||||
"width": 100
|
||||
},
|
||||
"tools_env": {
|
||||
"stretch": QtWidgets.QHeaderView.Interactive,
|
||||
"width": 140
|
||||
},
|
||||
"pixelAspect": {
|
||||
"stretch": QtWidgets.QHeaderView.Interactive,
|
||||
"width": 80
|
||||
}
|
||||
}
|
||||
persistent_columns = {
|
||||
"type",
|
||||
"frameStart",
|
||||
"frameEnd",
|
||||
"fps",
|
||||
"resolutionWidth",
|
||||
"resolutionHeight",
|
||||
"handleStart",
|
||||
"handleEnd",
|
||||
"clipIn",
|
||||
"clipOut",
|
||||
"pixelAspect",
|
||||
"tools_env"
|
||||
}
|
||||
|
||||
def __init__(self, dbcon, source_model, parent):
|
||||
super(HierarchyView, self).__init__(parent)
|
||||
# Direct access to model
|
||||
self._source_model = source_model
|
||||
self._editors_mapping = {}
|
||||
self._persisten_editors = set()
|
||||
# Access to parent because of `show_message` method
|
||||
self._parent = parent
|
||||
|
||||
project_doc_cache = ProjectDocCache(dbcon)
|
||||
tools_cache = ToolsCache()
|
||||
|
||||
main_delegate = QtWidgets.QStyledItemDelegate()
|
||||
self.setItemDelegate(main_delegate)
|
||||
self.setAlternatingRowColors(True)
|
||||
self.setSelectionMode(HierarchyView.ExtendedSelection)
|
||||
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
|
||||
|
||||
column_delegates = {}
|
||||
column_key_to_index = {}
|
||||
for key, item_type in self.column_delegate_defs.items():
|
||||
if isinstance(item_type, NameDef):
|
||||
delegate = NameDelegate()
|
||||
|
||||
elif isinstance(item_type, NumberDef):
|
||||
delegate = NumberDelegate(
|
||||
item_type.minimum,
|
||||
item_type.maximum,
|
||||
item_type.decimals
|
||||
)
|
||||
|
||||
elif isinstance(item_type, TypeDef):
|
||||
delegate = TypeDelegate(project_doc_cache)
|
||||
|
||||
elif isinstance(item_type, ToolsDef):
|
||||
delegate = ToolsDelegate(tools_cache)
|
||||
|
||||
column = self._source_model.columns.index(key)
|
||||
self.setItemDelegateForColumn(column, delegate)
|
||||
column_delegates[key] = delegate
|
||||
column_key_to_index[key] = column
|
||||
|
||||
source_model.index_moved.connect(self._on_rows_moved)
|
||||
self.customContextMenuRequested.connect(self._on_context_menu)
|
||||
self._source_model.project_changed.connect(self._on_project_reset)
|
||||
|
||||
self._project_doc_cache = project_doc_cache
|
||||
self._tools_cache = tools_cache
|
||||
|
||||
self._delegate = main_delegate
|
||||
self._column_delegates = column_delegates
|
||||
self._column_key_to_index = column_key_to_index
|
||||
|
||||
def header_init(self):
|
||||
header = self.header()
|
||||
header.setStretchLastSection(False)
|
||||
|
||||
default_behavior = self.columns_sizes["default"]
|
||||
widths_by_idx = {}
|
||||
for idx in range(header.count()):
|
||||
key = self._source_model.columns[idx]
|
||||
behavior = self.columns_sizes.get(key, default_behavior)
|
||||
logical_index = header.logicalIndex(idx)
|
||||
stretch = behavior["stretch"]
|
||||
header.setSectionResizeMode(logical_index, stretch)
|
||||
width = behavior.get("width")
|
||||
if width is not None:
|
||||
widths_by_idx[idx] = width
|
||||
|
||||
for idx, width in widths_by_idx.items():
|
||||
self.setColumnWidth(idx, width)
|
||||
|
||||
def set_project(self, project_name):
|
||||
# Trigger helpers first
|
||||
self._project_doc_cache.set_project(project_name)
|
||||
self._tools_cache.refresh()
|
||||
|
||||
# Trigger update of model after all data for delegates are filled
|
||||
self._source_model.set_project(project_name)
|
||||
|
||||
def _on_project_reset(self):
|
||||
self.header_init()
|
||||
|
||||
self.collapseAll()
|
||||
|
||||
project_item = self._source_model.project_item
|
||||
if project_item:
|
||||
index = self._source_model.index_for_item(project_item)
|
||||
self.expand(index)
|
||||
|
||||
def _on_rows_moved(self, index):
|
||||
parent_index = index.parent()
|
||||
if not self.isExpanded(parent_index):
|
||||
self.expand(parent_index)
|
||||
|
||||
def commitData(self, editor):
|
||||
super(HierarchyView, self).commitData(editor)
|
||||
current_index = self.currentIndex()
|
||||
column = current_index.column()
|
||||
row = current_index.row()
|
||||
skipped_index = None
|
||||
# Change column from "type" to "name"
|
||||
if column == 1:
|
||||
new_index = self._source_model.index(
|
||||
current_index.row(),
|
||||
0,
|
||||
current_index.parent()
|
||||
)
|
||||
self.setCurrentIndex(new_index)
|
||||
elif column > 0:
|
||||
indexes = []
|
||||
for index in self.selectedIndexes():
|
||||
if index.column() == column:
|
||||
if index.row() == row:
|
||||
skipped_index = index
|
||||
else:
|
||||
indexes.append(index)
|
||||
|
||||
if skipped_index is not None:
|
||||
value = current_index.data(QtCore.Qt.EditRole)
|
||||
for index in indexes:
|
||||
index.model().setData(index, value, QtCore.Qt.EditRole)
|
||||
|
||||
# Update children data
|
||||
self.updateEditorData()
|
||||
|
||||
def _deselect_editor(self, editor):
|
||||
if editor:
|
||||
if isinstance(
|
||||
editor, (QtWidgets.QSpinBox, QtWidgets.QDoubleSpinBox)
|
||||
):
|
||||
line_edit = editor.findChild(QtWidgets.QLineEdit)
|
||||
line_edit.deselect()
|
||||
|
||||
elif isinstance(editor, QtWidgets.QLineEdit):
|
||||
editor.deselect()
|
||||
|
||||
def edit(self, index, *args, **kwargs):
|
||||
result = super(HierarchyView, self).edit(index, *args, **kwargs)
|
||||
if result:
|
||||
# Mark index to not return text for DisplayRole
|
||||
editor = self.indexWidget(index)
|
||||
if (
|
||||
editor not in self._persisten_editors
|
||||
and editor not in self._editors_mapping
|
||||
):
|
||||
self._editors_mapping[editor] = index
|
||||
self._source_model.setData(index, True, EDITOR_OPENED_ROLE)
|
||||
# Deselect content of editor
|
||||
# QUESTION not sure if we want do this all the time
|
||||
self._deselect_editor(editor)
|
||||
return result
|
||||
|
||||
def closeEditor(self, editor, hint):
|
||||
if (
|
||||
editor not in self._persisten_editors
|
||||
and editor in self._editors_mapping
|
||||
):
|
||||
index = self._editors_mapping.pop(editor)
|
||||
self._source_model.setData(index, False, EDITOR_OPENED_ROLE)
|
||||
super(HierarchyView, self).closeEditor(editor, hint)
|
||||
|
||||
def openPersistentEditor(self, index):
|
||||
self._source_model.setData(index, True, EDITOR_OPENED_ROLE)
|
||||
super(HierarchyView, self).openPersistentEditor(index)
|
||||
editor = self.indexWidget(index)
|
||||
self._persisten_editors.add(editor)
|
||||
self._deselect_editor(editor)
|
||||
|
||||
def closePersistentEditor(self, index):
|
||||
self._source_model.setData(index, False, EDITOR_OPENED_ROLE)
|
||||
editor = self.indexWidget(index)
|
||||
self._persisten_editors.remove(editor)
|
||||
super(HierarchyView, self).closePersistentEditor(index)
|
||||
|
||||
def rowsInserted(self, parent_index, start, end):
|
||||
super(HierarchyView, self).rowsInserted(parent_index, start, end)
|
||||
|
||||
for row in range(start, end + 1):
|
||||
for key, column in self._column_key_to_index.items():
|
||||
if key not in self.persistent_columns:
|
||||
continue
|
||||
col_index = self._source_model.index(row, column, parent_index)
|
||||
if bool(
|
||||
self._source_model.flags(col_index)
|
||||
& QtCore.Qt.ItemIsEditable
|
||||
):
|
||||
self.openPersistentEditor(col_index)
|
||||
|
||||
# Expand parent on insert
|
||||
if not self.isExpanded(parent_index):
|
||||
self.expand(parent_index)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
index = self.indexAt(event.pos())
|
||||
if not index.isValid():
|
||||
# clear the selection
|
||||
self.clearSelection()
|
||||
# clear the current index
|
||||
self.setCurrentIndex(QtCore.QModelIndex())
|
||||
|
||||
super(HierarchyView, self).mousePressEvent(event)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
call_super = False
|
||||
if event.key() == QtCore.Qt.Key_Delete:
|
||||
self._delete_items()
|
||||
|
||||
elif event.matches(QtGui.QKeySequence.Copy):
|
||||
self._copy_items()
|
||||
|
||||
elif event.matches(QtGui.QKeySequence.Paste):
|
||||
self._paste_items()
|
||||
|
||||
elif event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
|
||||
mdfs = event.modifiers()
|
||||
if mdfs == (QtCore.Qt.ShiftModifier | QtCore.Qt.ControlModifier):
|
||||
self._on_ctrl_shift_enter_pressed()
|
||||
elif mdfs == QtCore.Qt.ShiftModifier:
|
||||
self._on_shift_enter_pressed()
|
||||
else:
|
||||
if self.state() == HierarchyView.NoState:
|
||||
self._on_enter_pressed()
|
||||
|
||||
elif event.modifiers() == QtCore.Qt.ControlModifier:
|
||||
if event.key() == QtCore.Qt.Key_Left:
|
||||
self._on_left_ctrl_pressed()
|
||||
elif event.key() == QtCore.Qt.Key_Right:
|
||||
self._on_right_ctrl_pressed()
|
||||
elif event.key() == QtCore.Qt.Key_Up:
|
||||
self._on_up_ctrl_pressed()
|
||||
elif event.key() == QtCore.Qt.Key_Down:
|
||||
self._on_down_ctrl_pressed()
|
||||
else:
|
||||
call_super = True
|
||||
|
||||
if call_super:
|
||||
super(HierarchyView, self).keyPressEvent(event)
|
||||
else:
|
||||
event.accept()
|
||||
|
||||
def _copy_items(self, indexes=None):
|
||||
try:
|
||||
if indexes is None:
|
||||
indexes = self.selectedIndexes()
|
||||
mime_data = self._source_model.copy_mime_data(indexes)
|
||||
|
||||
QtWidgets.QApplication.clipboard().setMimeData(mime_data)
|
||||
self._show_message("Tasks copied")
|
||||
except ValueError as exc:
|
||||
self._show_message(str(exc))
|
||||
|
||||
def _paste_items(self):
|
||||
index = self.currentIndex()
|
||||
mime_data = QtWidgets.QApplication.clipboard().mimeData()
|
||||
self._source_model.paste_mime_data(index, mime_data)
|
||||
|
||||
def _delete_items(self, indexes=None):
|
||||
if indexes is None:
|
||||
indexes = self.selectedIndexes()
|
||||
self._source_model.delete_indexes(indexes)
|
||||
|
||||
def _on_ctrl_shift_enter_pressed(self):
|
||||
self._add_task_and_edit()
|
||||
|
||||
def add_asset(self, parent_index=None):
|
||||
if parent_index is None:
|
||||
parent_index = self.currentIndex()
|
||||
|
||||
if not parent_index.isValid():
|
||||
return
|
||||
|
||||
# Stop editing
|
||||
self.setState(HierarchyView.NoState)
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
return self._source_model.add_new_asset(parent_index)
|
||||
|
||||
def add_task(self, parent_index=None):
|
||||
if parent_index is None:
|
||||
parent_index = self.currentIndex()
|
||||
|
||||
if not parent_index.isValid():
|
||||
return
|
||||
|
||||
return self._source_model.add_new_task(parent_index)
|
||||
|
||||
def _add_asset_and_edit(self, parent_index=None):
|
||||
new_index = self.add_asset(parent_index)
|
||||
if new_index is None:
|
||||
return
|
||||
|
||||
# Change current index
|
||||
self.selectionModel().setCurrentIndex(
|
||||
new_index,
|
||||
QtCore.QItemSelectionModel.Clear
|
||||
| QtCore.QItemSelectionModel.Select
|
||||
)
|
||||
# Start editing
|
||||
self.edit(new_index)
|
||||
|
||||
def _add_task_and_edit(self):
|
||||
new_index = self.add_task()
|
||||
if new_index is None:
|
||||
return
|
||||
|
||||
# Stop editing
|
||||
self.setState(HierarchyView.NoState)
|
||||
QtWidgets.QApplication.processEvents()
|
||||
|
||||
# TODO change hardcoded column index to coded
|
||||
task_type_index = self._source_model.index(
|
||||
new_index.row(), 1, new_index.parent()
|
||||
)
|
||||
# Change current index
|
||||
self.selectionModel().setCurrentIndex(
|
||||
task_type_index,
|
||||
QtCore.QItemSelectionModel.Clear
|
||||
| QtCore.QItemSelectionModel.Select
|
||||
)
|
||||
# Start editing
|
||||
self.edit(task_type_index)
|
||||
|
||||
def _on_shift_enter_pressed(self):
|
||||
parent_index = self.currentIndex()
|
||||
if not parent_index.isValid():
|
||||
return
|
||||
|
||||
if parent_index.data(ITEM_TYPE_ROLE) == "asset":
|
||||
parent_index = parent_index.parent()
|
||||
self._add_asset_and_edit(parent_index)
|
||||
|
||||
def _on_up_ctrl_pressed(self):
|
||||
indexes = self.selectedIndexes()
|
||||
self._source_model.move_vertical(indexes, -1)
|
||||
|
||||
def _on_down_ctrl_pressed(self):
|
||||
indexes = self.selectedIndexes()
|
||||
self._source_model.move_vertical(indexes, 1)
|
||||
|
||||
def _on_left_ctrl_pressed(self):
|
||||
indexes = self.selectedIndexes()
|
||||
self._source_model.move_horizontal(indexes, -1)
|
||||
|
||||
def _on_right_ctrl_pressed(self):
|
||||
indexes = self.selectedIndexes()
|
||||
self._source_model.move_horizontal(indexes, 1)
|
||||
|
||||
def _on_enter_pressed(self):
|
||||
index = self.currentIndex()
|
||||
if (
|
||||
index.isValid()
|
||||
and index.flags() & QtCore.Qt.ItemIsEditable
|
||||
):
|
||||
self.edit(index)
|
||||
|
||||
def _remove_delete_flag(self, item_ids):
|
||||
"""Remove deletion flag on items marked for deletion."""
|
||||
self._source_model.remove_delete_flag(item_ids)
|
||||
|
||||
def _expand_items(self, indexes):
|
||||
"""Expand multiple items with all it's children.
|
||||
|
||||
Args:
|
||||
indexes (list): List of QModelIndex that should be expanded.
|
||||
"""
|
||||
process_queue = Queue()
|
||||
for index in indexes:
|
||||
if index.column() == 0:
|
||||
process_queue.put(index)
|
||||
|
||||
item_ids = set()
|
||||
# Use deque as expanding not visible items as first is faster
|
||||
indexes_deque = collections.deque()
|
||||
while not process_queue.empty():
|
||||
index = process_queue.get()
|
||||
item_id = index.data(IDENTIFIER_ROLE)
|
||||
if item_id in item_ids:
|
||||
continue
|
||||
item_ids.add(item_id)
|
||||
|
||||
indexes_deque.append(index)
|
||||
|
||||
for row in range(self._source_model.rowCount(index)):
|
||||
process_queue.put(self._source_model.index(
|
||||
row, 0, index
|
||||
))
|
||||
|
||||
while indexes_deque:
|
||||
self.expand(indexes_deque.pop())
|
||||
|
||||
def _collapse_items(self, indexes):
|
||||
"""Collapse multiple items with all it's children.
|
||||
|
||||
Args:
|
||||
indexes (list): List of QModelIndex that should be collapsed.
|
||||
"""
|
||||
item_ids = set()
|
||||
process_queue = Queue()
|
||||
for index in indexes:
|
||||
if index.column() == 0:
|
||||
process_queue.put(index)
|
||||
|
||||
while not process_queue.empty():
|
||||
index = process_queue.get()
|
||||
item_id = index.data(IDENTIFIER_ROLE)
|
||||
if item_id in item_ids:
|
||||
continue
|
||||
item_ids.add(item_id)
|
||||
|
||||
self.collapse(index)
|
||||
|
||||
for row in range(self._source_model.rowCount(index)):
|
||||
process_queue.put(self._source_model.index(
|
||||
row, 0, index
|
||||
))
|
||||
|
||||
def _show_message(self, message):
|
||||
"""Show message to user."""
|
||||
self._parent.show_message(message)
|
||||
|
||||
def _on_context_menu(self, point):
|
||||
"""Context menu on right click.
|
||||
|
||||
Currently is menu shown only on "name" column.
|
||||
"""
|
||||
index = self.indexAt(point)
|
||||
column = index.column()
|
||||
if column != 0:
|
||||
return
|
||||
|
||||
actions = []
|
||||
|
||||
context_menu = QtWidgets.QMenu(self)
|
||||
|
||||
indexes = self.selectedIndexes()
|
||||
|
||||
items_by_id = {}
|
||||
for index in indexes:
|
||||
if index.column() != column:
|
||||
continue
|
||||
|
||||
item_id = index.data(IDENTIFIER_ROLE)
|
||||
items_by_id[item_id] = self._source_model.items_by_id[item_id]
|
||||
|
||||
item_ids = tuple(items_by_id.keys())
|
||||
if len(item_ids) == 1:
|
||||
item = items_by_id[item_ids[0]]
|
||||
item_type = item.data(ITEM_TYPE_ROLE)
|
||||
if item_type in ("asset", "project"):
|
||||
add_asset_action = QtWidgets.QAction("Add Asset", context_menu)
|
||||
add_asset_action.triggered.connect(
|
||||
self._add_asset_and_edit
|
||||
)
|
||||
actions.append(add_asset_action)
|
||||
|
||||
if item_type in ("asset", "task"):
|
||||
add_task_action = QtWidgets.QAction("Add Task", context_menu)
|
||||
add_task_action.triggered.connect(
|
||||
self._add_task_and_edit
|
||||
)
|
||||
actions.append(add_task_action)
|
||||
|
||||
# Remove delete tag on items
|
||||
removed_item_ids = []
|
||||
show_delete_items = False
|
||||
for item_id, item in items_by_id.items():
|
||||
if item.data(REMOVED_ROLE):
|
||||
removed_item_ids.append(item_id)
|
||||
elif (
|
||||
not show_delete_items
|
||||
and item.data(ITEM_TYPE_ROLE) != "project"
|
||||
and item.data(HIERARCHY_CHANGE_ABLE_ROLE)
|
||||
):
|
||||
show_delete_items = True
|
||||
|
||||
if show_delete_items:
|
||||
action = QtWidgets.QAction("Delete items", context_menu)
|
||||
action.triggered.connect(
|
||||
lambda: self._delete_items()
|
||||
)
|
||||
actions.append(action)
|
||||
|
||||
if removed_item_ids:
|
||||
action = QtWidgets.QAction("Keep items", context_menu)
|
||||
action.triggered.connect(
|
||||
lambda: self._remove_delete_flag(removed_item_ids)
|
||||
)
|
||||
actions.append(action)
|
||||
|
||||
# Collapse/Expand action
|
||||
show_collapse_expand_action = False
|
||||
for item_id in item_ids:
|
||||
item = items_by_id[item_id]
|
||||
item_type = item.data(ITEM_TYPE_ROLE)
|
||||
if item_type != "task":
|
||||
show_collapse_expand_action = True
|
||||
break
|
||||
|
||||
if show_collapse_expand_action:
|
||||
expand_action = QtWidgets.QAction("Expand all", context_menu)
|
||||
collapse_action = QtWidgets.QAction("Collapse all", context_menu)
|
||||
expand_action.triggered.connect(
|
||||
lambda: self._expand_items(indexes)
|
||||
)
|
||||
collapse_action.triggered.connect(
|
||||
lambda: self._collapse_items(indexes)
|
||||
)
|
||||
actions.append(expand_action)
|
||||
actions.append(collapse_action)
|
||||
|
||||
if not actions:
|
||||
return
|
||||
|
||||
for action in actions:
|
||||
context_menu.addAction(action)
|
||||
|
||||
global_point = self.viewport().mapToGlobal(point)
|
||||
context_menu.exec_(global_point)
|
||||
281
openpype/tools/project_manager/project_manager/widgets.py
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
import re
|
||||
|
||||
from .constants import (
|
||||
NAME_ALLOWED_SYMBOLS,
|
||||
NAME_REGEX
|
||||
)
|
||||
from openpype.lib import (
|
||||
create_project,
|
||||
PROJECT_NAME_ALLOWED_SYMBOLS,
|
||||
PROJECT_NAME_REGEX
|
||||
)
|
||||
from avalon.api import AvalonMongoDB
|
||||
|
||||
from Qt import QtWidgets, QtCore
|
||||
|
||||
|
||||
class NameTextEdit(QtWidgets.QLineEdit):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NameTextEdit, self).__init__(*args, **kwargs)
|
||||
|
||||
self.textChanged.connect(self._on_text_change)
|
||||
|
||||
def _on_text_change(self, text):
|
||||
if NAME_REGEX.match(text):
|
||||
return
|
||||
|
||||
idx = self.cursorPosition()
|
||||
before_text = text[0:idx]
|
||||
after_text = text[idx:len(text)]
|
||||
sub_regex = "[^{}]+".format(NAME_ALLOWED_SYMBOLS)
|
||||
new_before_text = re.sub(sub_regex, "", before_text)
|
||||
new_after_text = re.sub(sub_regex, "", after_text)
|
||||
idx -= (len(before_text) - len(new_before_text))
|
||||
|
||||
self.setText(new_before_text + new_after_text)
|
||||
self.setCursorPosition(idx)
|
||||
|
||||
|
||||
class FilterComboBox(QtWidgets.QComboBox):
|
||||
def __init__(self, parent=None):
|
||||
super(FilterComboBox, self).__init__(parent)
|
||||
|
||||
self._last_value = None
|
||||
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
self.setEditable(True)
|
||||
|
||||
filter_proxy_model = QtCore.QSortFilterProxyModel(self)
|
||||
filter_proxy_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||
filter_proxy_model.setSourceModel(self.model())
|
||||
|
||||
completer = QtWidgets.QCompleter(filter_proxy_model, self)
|
||||
completer.setCompletionMode(
|
||||
QtWidgets.QCompleter.UnfilteredPopupCompletion
|
||||
)
|
||||
self.setCompleter(completer)
|
||||
|
||||
self.lineEdit().textEdited.connect(
|
||||
filter_proxy_model.setFilterFixedString
|
||||
)
|
||||
completer.activated.connect(self.on_completer_activated)
|
||||
|
||||
self._completer = completer
|
||||
self._filter_proxy_model = filter_proxy_model
|
||||
|
||||
def focusInEvent(self, event):
|
||||
super(FilterComboBox, self).focusInEvent(event)
|
||||
self._last_value = self.lineEdit().text()
|
||||
self.lineEdit().selectAll()
|
||||
|
||||
def value_cleanup(self):
|
||||
text = self.lineEdit().text()
|
||||
idx = self.findText(text)
|
||||
if idx < 0:
|
||||
count = self._completer.completionModel().rowCount()
|
||||
if count > 0:
|
||||
index = self._completer.completionModel().index(0, 0)
|
||||
text = index.data(QtCore.Qt.DisplayRole)
|
||||
idx = self.findText(text)
|
||||
elif self._last_value is not None:
|
||||
idx = self.findText(self._last_value)
|
||||
|
||||
if idx < 0:
|
||||
idx = 0
|
||||
self.setCurrentIndex(idx)
|
||||
|
||||
def on_completer_activated(self, text):
|
||||
if text:
|
||||
index = self.findText(text)
|
||||
self.setCurrentIndex(index)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
|
||||
self.value_cleanup()
|
||||
|
||||
super(FilterComboBox, self).keyPressEvent(event)
|
||||
|
||||
def setModel(self, model):
|
||||
super(FilterComboBox, self).setModel(model)
|
||||
self._filter_proxy_model.setSourceModel(model)
|
||||
self._completer.setModel(self._filter_proxy_model)
|
||||
|
||||
def setModelColumn(self, column):
|
||||
self._completer.setCompletionColumn(column)
|
||||
self._filter_proxy_model.setFilterKeyColumn(column)
|
||||
super(FilterComboBox, self).setModelColumn(column)
|
||||
|
||||
|
||||
class CreateProjectDialog(QtWidgets.QDialog):
|
||||
def __init__(self, parent=None, dbcon=None):
|
||||
super(CreateProjectDialog, self).__init__(parent)
|
||||
|
||||
self.setWindowTitle("Create Project")
|
||||
|
||||
self.allowed_regex = "[^{}]+".format(PROJECT_NAME_ALLOWED_SYMBOLS)
|
||||
|
||||
if dbcon is None:
|
||||
dbcon = AvalonMongoDB()
|
||||
|
||||
self.dbcon = dbcon
|
||||
self._ignore_code_change = False
|
||||
self._project_name_is_valid = False
|
||||
self._project_code_is_valid = False
|
||||
self._project_code_value = None
|
||||
|
||||
project_names, project_codes = self._get_existing_projects()
|
||||
|
||||
inputs_widget = QtWidgets.QWidget(self)
|
||||
project_name_input = QtWidgets.QLineEdit(inputs_widget)
|
||||
project_code_input = QtWidgets.QLineEdit(inputs_widget)
|
||||
library_project_input = QtWidgets.QCheckBox(inputs_widget)
|
||||
|
||||
inputs_layout = QtWidgets.QFormLayout(inputs_widget)
|
||||
inputs_layout.setContentsMargins(0, 0, 0, 0)
|
||||
inputs_layout.addRow("Project name:", project_name_input)
|
||||
inputs_layout.addRow("Project code:", project_code_input)
|
||||
inputs_layout.addRow("Library project:", library_project_input)
|
||||
|
||||
project_name_label = QtWidgets.QLabel(self)
|
||||
project_code_label = QtWidgets.QLabel(self)
|
||||
|
||||
btns_widget = QtWidgets.QWidget(self)
|
||||
ok_btn = QtWidgets.QPushButton("Ok", btns_widget)
|
||||
ok_btn.setEnabled(False)
|
||||
cancel_btn = QtWidgets.QPushButton("Cancel", btns_widget)
|
||||
btns_layout = QtWidgets.QHBoxLayout(btns_widget)
|
||||
btns_layout.setContentsMargins(0, 0, 0, 0)
|
||||
btns_layout.addStretch(1)
|
||||
btns_layout.addWidget(ok_btn)
|
||||
btns_layout.addWidget(cancel_btn)
|
||||
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.addWidget(inputs_widget, 0)
|
||||
main_layout.addWidget(project_name_label, 1)
|
||||
main_layout.addWidget(project_code_label, 1)
|
||||
main_layout.addStretch(1)
|
||||
main_layout.addWidget(btns_widget, 0)
|
||||
|
||||
project_name_input.textChanged.connect(self._on_project_name_change)
|
||||
project_code_input.textChanged.connect(self._on_project_code_change)
|
||||
ok_btn.clicked.connect(self._on_ok_clicked)
|
||||
cancel_btn.clicked.connect(self._on_cancel_clicked)
|
||||
|
||||
self.invalid_project_names = project_names
|
||||
self.invalid_project_codes = project_codes
|
||||
|
||||
self.project_name_label = project_name_label
|
||||
self.project_code_label = project_code_label
|
||||
|
||||
self.project_name_input = project_name_input
|
||||
self.project_code_input = project_code_input
|
||||
self.library_project_input = library_project_input
|
||||
|
||||
self.ok_btn = ok_btn
|
||||
|
||||
@property
|
||||
def project_name(self):
|
||||
return self.project_name_input.text()
|
||||
|
||||
def _on_project_name_change(self, value):
|
||||
if self._project_code_value is None:
|
||||
self._ignore_code_change = True
|
||||
self.project_code_input.setText(value.lower())
|
||||
self._ignore_code_change = False
|
||||
|
||||
self._update_valid_project_name(value)
|
||||
|
||||
def _on_project_code_change(self, value):
|
||||
if not value:
|
||||
value = None
|
||||
|
||||
self._update_valid_project_code(value)
|
||||
|
||||
if not self._ignore_code_change:
|
||||
self._project_code_value = value
|
||||
|
||||
def _update_valid_project_name(self, value):
|
||||
message = ""
|
||||
is_valid = True
|
||||
if not value:
|
||||
message = "Project name is empty"
|
||||
is_valid = False
|
||||
|
||||
elif value in self.invalid_project_names:
|
||||
message = "Project name \"{}\" already exist".format(value)
|
||||
is_valid = False
|
||||
|
||||
elif not PROJECT_NAME_REGEX.match(value):
|
||||
message = (
|
||||
"Project name \"{}\" contain not supported symbols"
|
||||
).format(value)
|
||||
is_valid = False
|
||||
|
||||
self._project_name_is_valid = is_valid
|
||||
self.project_name_label.setText(message)
|
||||
self.project_name_label.setVisible(bool(message))
|
||||
self._enable_button()
|
||||
|
||||
def _update_valid_project_code(self, value):
|
||||
message = ""
|
||||
is_valid = True
|
||||
if not value:
|
||||
message = "Project code is empty"
|
||||
is_valid = False
|
||||
|
||||
elif value in self.invalid_project_names:
|
||||
message = "Project code \"{}\" already exist".format(value)
|
||||
is_valid = False
|
||||
|
||||
elif not PROJECT_NAME_REGEX.match(value):
|
||||
message = (
|
||||
"Project code \"{}\" contain not supported symbols"
|
||||
).format(value)
|
||||
is_valid = False
|
||||
|
||||
self._project_code_is_valid = is_valid
|
||||
self.project_code_label.setText(message)
|
||||
self._enable_button()
|
||||
|
||||
def _enable_button(self):
|
||||
self.ok_btn.setEnabled(
|
||||
self._project_name_is_valid and self._project_code_is_valid
|
||||
)
|
||||
|
||||
def _on_cancel_clicked(self):
|
||||
self.done(0)
|
||||
|
||||
def _on_ok_clicked(self):
|
||||
if not self._project_name_is_valid or not self._project_code_is_valid:
|
||||
return
|
||||
|
||||
project_name = self.project_name_input.text()
|
||||
project_code = self.project_code_input.text()
|
||||
library_project = self.library_project_input.isChecked()
|
||||
create_project(project_name, project_code, library_project, self.dbcon)
|
||||
|
||||
self.done(1)
|
||||
|
||||
def _get_existing_projects(self):
|
||||
project_names = set()
|
||||
project_codes = set()
|
||||
for project_name in self.dbcon.database.collection_names():
|
||||
# Each collection will have exactly one project document
|
||||
project_doc = self.dbcon.database[project_name].find_one(
|
||||
{"type": "project"},
|
||||
{"name": 1, "data.code": 1}
|
||||
)
|
||||
if not project_doc:
|
||||
continue
|
||||
|
||||
project_name = project_doc.get("name")
|
||||
if not project_name:
|
||||
continue
|
||||
|
||||
project_names.add(project_name)
|
||||
project_code = project_doc.get("data", {}).get("code")
|
||||
if not project_code:
|
||||
project_code = project_name
|
||||
|
||||
project_codes.add(project_code)
|
||||
return project_names, project_codes
|
||||
176
openpype/tools/project_manager/project_manager/window.py
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
from . import (
|
||||
ProjectModel,
|
||||
|
||||
HierarchyModel,
|
||||
HierarchySelectionModel,
|
||||
HierarchyView,
|
||||
|
||||
CreateProjectDialog
|
||||
)
|
||||
from .style import load_stylesheet, ResourceCache
|
||||
|
||||
from openpype import resources
|
||||
from avalon.api import AvalonMongoDB
|
||||
|
||||
|
||||
class ProjectManagerWindow(QtWidgets.QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super(ProjectManagerWindow, self).__init__(parent)
|
||||
|
||||
self.setWindowTitle("OpenPype Project Manager")
|
||||
self.setWindowIcon(QtGui.QIcon(resources.pype_icon_filepath()))
|
||||
|
||||
# Top part of window
|
||||
top_part_widget = QtWidgets.QWidget(self)
|
||||
|
||||
# Project selection
|
||||
project_widget = QtWidgets.QWidget(top_part_widget)
|
||||
|
||||
dbcon = AvalonMongoDB()
|
||||
|
||||
project_model = ProjectModel(dbcon)
|
||||
project_combobox = QtWidgets.QComboBox(project_widget)
|
||||
project_combobox.setModel(project_model)
|
||||
project_combobox.setRootModelIndex(QtCore.QModelIndex())
|
||||
|
||||
refresh_projects_btn = QtWidgets.QPushButton(project_widget)
|
||||
refresh_projects_btn.setIcon(ResourceCache.get_icon("refresh"))
|
||||
refresh_projects_btn.setToolTip("Refresh projects")
|
||||
refresh_projects_btn.setObjectName("RefreshBtn")
|
||||
|
||||
create_project_btn = QtWidgets.QPushButton(
|
||||
"Create project...", project_widget
|
||||
)
|
||||
|
||||
project_layout = QtWidgets.QHBoxLayout(project_widget)
|
||||
project_layout.setContentsMargins(0, 0, 0, 0)
|
||||
project_layout.addWidget(project_combobox, 0)
|
||||
project_layout.addWidget(refresh_projects_btn, 0)
|
||||
project_layout.addWidget(create_project_btn, 0)
|
||||
project_layout.addStretch(1)
|
||||
|
||||
# Helper buttons
|
||||
helper_btns_widget = QtWidgets.QWidget(top_part_widget)
|
||||
|
||||
helper_label = QtWidgets.QLabel("Add:", helper_btns_widget)
|
||||
add_asset_btn = QtWidgets.QPushButton(
|
||||
ResourceCache.get_icon("asset", "default"),
|
||||
"Asset",
|
||||
helper_btns_widget
|
||||
)
|
||||
add_task_btn = QtWidgets.QPushButton(
|
||||
ResourceCache.get_icon("task", "default"),
|
||||
"Task",
|
||||
helper_btns_widget
|
||||
)
|
||||
|
||||
helper_btns_layout = QtWidgets.QHBoxLayout(helper_btns_widget)
|
||||
helper_btns_layout.setContentsMargins(0, 0, 0, 0)
|
||||
helper_btns_layout.addWidget(helper_label)
|
||||
helper_btns_layout.addWidget(add_asset_btn)
|
||||
helper_btns_layout.addWidget(add_task_btn)
|
||||
helper_btns_layout.addStretch(1)
|
||||
|
||||
# Add widgets to top widget layout
|
||||
top_part_layout = QtWidgets.QVBoxLayout(top_part_widget)
|
||||
top_part_layout.setContentsMargins(0, 0, 0, 0)
|
||||
top_part_layout.addWidget(project_widget)
|
||||
top_part_layout.addWidget(helper_btns_widget)
|
||||
|
||||
hierarchy_model = HierarchyModel(dbcon)
|
||||
|
||||
hierarchy_view = HierarchyView(dbcon, hierarchy_model, self)
|
||||
hierarchy_view.setModel(hierarchy_model)
|
||||
|
||||
_selection_model = HierarchySelectionModel(
|
||||
hierarchy_model.multiselection_column_indexes
|
||||
)
|
||||
_selection_model.setModel(hierarchy_view.model())
|
||||
hierarchy_view.setSelectionModel(_selection_model)
|
||||
|
||||
buttons_widget = QtWidgets.QWidget(self)
|
||||
|
||||
message_label = QtWidgets.QLabel(buttons_widget)
|
||||
save_btn = QtWidgets.QPushButton("Save", buttons_widget)
|
||||
|
||||
buttons_layout = QtWidgets.QHBoxLayout(buttons_widget)
|
||||
buttons_layout.setContentsMargins(0, 0, 0, 0)
|
||||
buttons_layout.addWidget(message_label)
|
||||
buttons_layout.addStretch(1)
|
||||
buttons_layout.addWidget(save_btn)
|
||||
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.addWidget(top_part_widget)
|
||||
main_layout.addWidget(hierarchy_view)
|
||||
main_layout.addWidget(buttons_widget)
|
||||
|
||||
refresh_projects_btn.clicked.connect(self._on_project_refresh)
|
||||
create_project_btn.clicked.connect(self._on_project_create)
|
||||
project_combobox.currentIndexChanged.connect(self._on_project_change)
|
||||
save_btn.clicked.connect(self._on_save_click)
|
||||
add_asset_btn.clicked.connect(self._on_add_asset)
|
||||
add_task_btn.clicked.connect(self._on_add_task)
|
||||
|
||||
self.project_model = project_model
|
||||
self.project_combobox = project_combobox
|
||||
|
||||
self.hierarchy_view = hierarchy_view
|
||||
self.hierarchy_model = hierarchy_model
|
||||
|
||||
self.message_label = message_label
|
||||
|
||||
self.resize(1200, 600)
|
||||
self.setStyleSheet(load_stylesheet())
|
||||
|
||||
self.refresh_projects()
|
||||
|
||||
def _set_project(self, project_name=None):
|
||||
self.hierarchy_view.set_project(project_name)
|
||||
|
||||
def refresh_projects(self, project_name=None):
|
||||
if project_name is None:
|
||||
if self.project_combobox.count() > 0:
|
||||
project_name = self.project_combobox.currentText()
|
||||
|
||||
self.project_model.refresh()
|
||||
|
||||
if self.project_combobox.count() == 0:
|
||||
return self._set_project()
|
||||
|
||||
if project_name:
|
||||
row = self.project_combobox.findText(project_name)
|
||||
if row >= 0:
|
||||
self.project_combobox.setCurrentIndex(row)
|
||||
|
||||
self._set_project(self.project_combobox.currentText())
|
||||
|
||||
def _on_project_change(self):
|
||||
self._set_project(self.project_combobox.currentText())
|
||||
|
||||
def _on_project_refresh(self):
|
||||
self.refresh_projects()
|
||||
|
||||
def _on_save_click(self):
|
||||
self.hierarchy_model.save()
|
||||
|
||||
def _on_add_asset(self):
|
||||
self.hierarchy_view.add_asset()
|
||||
|
||||
def _on_add_task(self):
|
||||
self.hierarchy_view.add_task()
|
||||
|
||||
def show_message(self, message):
|
||||
# TODO add nicer message pop
|
||||
self.message_label.setText(message)
|
||||
|
||||
def _on_project_create(self):
|
||||
dialog = CreateProjectDialog(self)
|
||||
dialog.exec_()
|
||||
if dialog.result() != 1:
|
||||
return
|
||||
|
||||
project_name = dialog.project_name
|
||||
self.show_message("Created project \"{}\"".format(project_name))
|
||||
self.refresh_projects(project_name)
|
||||
|
|
@ -21,6 +21,7 @@ from openpype.settings.entities import (
|
|||
TextEntity,
|
||||
PathInput,
|
||||
RawJsonEntity,
|
||||
ColorEntity,
|
||||
|
||||
DefaultsNotDefined,
|
||||
StudioDefaultsNotDefined,
|
||||
|
|
@ -44,7 +45,7 @@ from .item_widgets import (
|
|||
PathWidget,
|
||||
PathInputWidget
|
||||
)
|
||||
|
||||
from .color_widget import ColorWidget
|
||||
from avalon.vendor import qtawesome
|
||||
|
||||
|
||||
|
|
@ -113,6 +114,9 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
elif isinstance(entity, RawJsonEntity):
|
||||
return RawJsonWidget(*args)
|
||||
|
||||
elif isinstance(entity, ColorEntity):
|
||||
return ColorWidget(*args)
|
||||
|
||||
elif isinstance(entity, BaseEnumEntity):
|
||||
return EnumeratorWidget(*args)
|
||||
|
||||
|
|
|
|||
171
openpype/tools/settings/settings/color_widget.py
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
from .item_widgets import InputWidget
|
||||
|
||||
from openpype.widgets.color_widgets import (
|
||||
ColorPickerWidget,
|
||||
draw_checkerboard_tile
|
||||
)
|
||||
|
||||
|
||||
class ColorWidget(InputWidget):
|
||||
def _add_inputs_to_layout(self):
|
||||
self.input_field = ColorViewer(self.content_widget)
|
||||
|
||||
self.setFocusProxy(self.input_field)
|
||||
|
||||
self.content_layout.addWidget(self.input_field, 1)
|
||||
|
||||
self.input_field.clicked.connect(self._on_click)
|
||||
|
||||
self._dialog = None
|
||||
|
||||
def _on_click(self):
|
||||
if self._dialog:
|
||||
self._dialog.open()
|
||||
return
|
||||
|
||||
dialog = ColorDialog(self.input_field.color(), self)
|
||||
self._dialog = dialog
|
||||
|
||||
dialog.open()
|
||||
dialog.finished.connect(self._on_dialog_finish)
|
||||
|
||||
def _on_dialog_finish(self, *_args):
|
||||
if not self._dialog:
|
||||
return
|
||||
|
||||
color = self._dialog.result()
|
||||
if color is not None:
|
||||
self.input_field.set_color(color)
|
||||
self._on_value_change()
|
||||
|
||||
self._dialog.deleteLater()
|
||||
self._dialog = None
|
||||
|
||||
def _on_entity_change(self):
|
||||
if self.entity.value != self.input_value():
|
||||
self.set_entity_value()
|
||||
|
||||
def set_entity_value(self):
|
||||
self.input_field.set_color(*self.entity.value)
|
||||
|
||||
def input_value(self):
|
||||
color = self.input_field.color()
|
||||
return [color.red(), color.green(), color.blue(), color.alpha()]
|
||||
|
||||
def _on_value_change(self):
|
||||
if self.ignore_input_changes:
|
||||
return
|
||||
|
||||
self.entity.set(self.input_value())
|
||||
|
||||
|
||||
class ColorViewer(QtWidgets.QWidget):
|
||||
clicked = QtCore.Signal()
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(ColorViewer, self).__init__(parent)
|
||||
|
||||
self.setMinimumSize(10, 10)
|
||||
|
||||
self.actual_pen = QtGui.QPen()
|
||||
self.actual_color = QtGui.QColor()
|
||||
self._checkerboard = None
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
self.clicked.emit()
|
||||
super(ColorViewer, self).mouseReleaseEvent(event)
|
||||
|
||||
def checkerboard(self):
|
||||
if not self._checkerboard:
|
||||
self._checkerboard = draw_checkerboard_tile(self.height() / 4)
|
||||
return self._checkerboard
|
||||
|
||||
def color(self):
|
||||
return self.actual_color
|
||||
|
||||
def set_color(self, *args):
|
||||
# Create copy of entered color
|
||||
self.actual_color = QtGui.QColor(*args)
|
||||
# Repaint
|
||||
self.update()
|
||||
|
||||
def set_alpha(self, alpha):
|
||||
# Change alpha of current color
|
||||
self.actual_color.setAlpha(alpha)
|
||||
# Repaint
|
||||
self.update()
|
||||
|
||||
def paintEvent(self, event):
|
||||
rect = event.rect()
|
||||
|
||||
painter = QtGui.QPainter(self)
|
||||
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
|
||||
radius = rect.height() / 2
|
||||
rounded_rect = QtGui.QPainterPath()
|
||||
rounded_rect.addRoundedRect(QtCore.QRectF(rect), radius, radius)
|
||||
painter.setClipPath(rounded_rect)
|
||||
|
||||
pen = QtGui.QPen(QtGui.QColor(255, 255, 255, 67))
|
||||
pen.setWidth(1)
|
||||
painter.setPen(pen)
|
||||
painter.drawTiledPixmap(rect, self.checkerboard())
|
||||
painter.fillRect(rect, self.actual_color)
|
||||
painter.drawPath(rounded_rect)
|
||||
|
||||
painter.end()
|
||||
|
||||
|
||||
class ColorDialog(QtWidgets.QDialog):
|
||||
def __init__(self, color=None, parent=None):
|
||||
super(ColorDialog, self).__init__(parent)
|
||||
|
||||
self.setWindowTitle("Color picker dialog")
|
||||
|
||||
picker_widget = ColorPickerWidget(color, self)
|
||||
|
||||
footer_widget = QtWidgets.QWidget(self)
|
||||
|
||||
ok_btn = QtWidgets.QPushButton("Ok", footer_widget)
|
||||
cancel_btn = QtWidgets.QPushButton("Cancel", footer_widget)
|
||||
|
||||
footer_layout = QtWidgets.QHBoxLayout(footer_widget)
|
||||
footer_layout.setContentsMargins(0, 0, 0, 0)
|
||||
footer_layout.addStretch(1)
|
||||
footer_layout.addWidget(ok_btn)
|
||||
footer_layout.addWidget(cancel_btn)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
|
||||
layout.addWidget(picker_widget, 1)
|
||||
layout.addWidget(footer_widget, 0)
|
||||
|
||||
ok_btn.clicked.connect(self.on_ok_clicked)
|
||||
cancel_btn.clicked.connect(self.on_cancel_clicked)
|
||||
|
||||
self.picker_widget = picker_widget
|
||||
self.ok_btn = ok_btn
|
||||
self.cancel_btn = cancel_btn
|
||||
|
||||
self._result = None
|
||||
|
||||
def showEvent(self, event):
|
||||
super(ColorDialog, self).showEvent(event)
|
||||
|
||||
btns_width = max(self.ok_btn.width(), self.cancel_btn.width())
|
||||
self.ok_btn.setFixedWidth(btns_width)
|
||||
self.cancel_btn.setFixedWidth(btns_width)
|
||||
|
||||
def on_ok_clicked(self):
|
||||
self._result = self.picker_widget.color()
|
||||
self.close()
|
||||
|
||||
def on_cancel_clicked(self):
|
||||
self._result = None
|
||||
self.close()
|
||||
|
||||
def result(self):
|
||||
return self._result
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""Package declaring Pype version."""
|
||||
__version__ = "3.0.0-rc4"
|
||||
__version__ = "3.0.0-rc.5"
|
||||
|
|
|
|||
14
openpype/widgets/color_widgets/__init__.py
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
from .color_picker_widget import (
|
||||
ColorPickerWidget
|
||||
)
|
||||
|
||||
from .color_view import (
|
||||
draw_checkerboard_tile
|
||||
)
|
||||
|
||||
|
||||
__all__ = (
|
||||
"ColorPickerWidget",
|
||||
|
||||
"draw_checkerboard_tile"
|
||||
)
|
||||
639
openpype/widgets/color_widgets/color_inputs.py
Normal file
|
|
@ -0,0 +1,639 @@
|
|||
import re
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
from .color_view import draw_checkerboard_tile
|
||||
|
||||
|
||||
slide_style = """
|
||||
QSlider::groove:horizontal {
|
||||
background: qlineargradient(
|
||||
x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #000, stop: 1 #fff
|
||||
);
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
QSlider::handle:horizontal {
|
||||
background: qlineargradient(
|
||||
x1:0, y1:0, x2:1, y2:1, stop:0 #ddd, stop:1 #bbb
|
||||
);
|
||||
border: 1px solid #777;
|
||||
width: 8px;
|
||||
margin-top: -1px;
|
||||
margin-bottom: -1px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
QSlider::handle:horizontal:hover {
|
||||
background: qlineargradient(
|
||||
x1:0, y1:0, x2:1, y2:1, stop:0 #eee, stop:1 #ddd
|
||||
);
|
||||
border: 1px solid #444;ff
|
||||
border-radius: 4px;
|
||||
}"""
|
||||
|
||||
|
||||
class AlphaSlider(QtWidgets.QSlider):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(AlphaSlider, self).__init__(*args, **kwargs)
|
||||
self._mouse_clicked = False
|
||||
self.setSingleStep(1)
|
||||
self.setMinimum(0)
|
||||
self.setMaximum(255)
|
||||
self.setValue(255)
|
||||
|
||||
self._checkerboard = None
|
||||
|
||||
def checkerboard(self):
|
||||
if self._checkerboard is None:
|
||||
self._checkerboard = draw_checkerboard_tile(
|
||||
3, QtGui.QColor(173, 173, 173), QtGui.QColor(27, 27, 27)
|
||||
)
|
||||
return self._checkerboard
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
self._mouse_clicked = True
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
self._set_value_to_pos(event.pos().x())
|
||||
return event.accept()
|
||||
return super(AlphaSlider, self).mousePressEvent(event)
|
||||
|
||||
def _set_value_to_pos(self, pos_x):
|
||||
value = (
|
||||
self.maximum() - self.minimum()
|
||||
) * pos_x / self.width() + self.minimum()
|
||||
self.setValue(value)
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
if self._mouse_clicked:
|
||||
self._set_value_to_pos(event.pos().x())
|
||||
super(AlphaSlider, self).mouseMoveEvent(event)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
self._mouse_clicked = True
|
||||
super(AlphaSlider, self).mouseReleaseEvent(event)
|
||||
|
||||
def paintEvent(self, event):
|
||||
painter = QtGui.QPainter(self)
|
||||
opt = QtWidgets.QStyleOptionSlider()
|
||||
self.initStyleOption(opt)
|
||||
|
||||
painter.fillRect(event.rect(), QtCore.Qt.transparent)
|
||||
|
||||
painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform)
|
||||
rect = self.style().subControlRect(
|
||||
QtWidgets.QStyle.CC_Slider,
|
||||
opt,
|
||||
QtWidgets.QStyle.SC_SliderGroove,
|
||||
self
|
||||
)
|
||||
final_height = 9
|
||||
offset_top = 0
|
||||
if rect.height() > final_height:
|
||||
offset_top = int((rect.height() - final_height) / 2)
|
||||
rect = QtCore.QRect(
|
||||
rect.x(),
|
||||
offset_top,
|
||||
rect.width(),
|
||||
final_height
|
||||
)
|
||||
|
||||
pix_rect = QtCore.QRect(event.rect())
|
||||
pix_rect.setX(rect.x())
|
||||
pix_rect.setWidth(rect.width() - (2 * rect.x()))
|
||||
pix = QtGui.QPixmap(pix_rect.width(), pix_rect.height())
|
||||
pix_painter = QtGui.QPainter(pix)
|
||||
pix_painter.drawTiledPixmap(pix_rect, self.checkerboard())
|
||||
gradient = QtGui.QLinearGradient(rect.topLeft(), rect.bottomRight())
|
||||
gradient.setColorAt(0, QtCore.Qt.transparent)
|
||||
gradient.setColorAt(1, QtCore.Qt.white)
|
||||
pix_painter.fillRect(pix_rect, gradient)
|
||||
pix_painter.end()
|
||||
|
||||
brush = QtGui.QBrush(pix)
|
||||
painter.save()
|
||||
painter.setPen(QtCore.Qt.NoPen)
|
||||
painter.setBrush(brush)
|
||||
ratio = rect.height() / 2
|
||||
painter.drawRoundedRect(rect, ratio, ratio)
|
||||
painter.restore()
|
||||
|
||||
_handle_rect = self.style().subControlRect(
|
||||
QtWidgets.QStyle.CC_Slider,
|
||||
opt,
|
||||
QtWidgets.QStyle.SC_SliderHandle,
|
||||
self
|
||||
)
|
||||
|
||||
handle_rect = QtCore.QRect(rect)
|
||||
if offset_top > 1:
|
||||
height = handle_rect.height()
|
||||
handle_rect.setY(handle_rect.y() - 1)
|
||||
handle_rect.setHeight(height + 2)
|
||||
handle_rect.setX(_handle_rect.x())
|
||||
handle_rect.setWidth(handle_rect.height())
|
||||
|
||||
painter.save()
|
||||
|
||||
gradient = QtGui.QRadialGradient()
|
||||
radius = handle_rect.height() / 2
|
||||
center_x = handle_rect.width() / 2 + handle_rect.x()
|
||||
center_y = handle_rect.height()
|
||||
gradient.setCenter(center_x, center_y)
|
||||
gradient.setCenterRadius(radius)
|
||||
gradient.setFocalPoint(center_x, center_y)
|
||||
|
||||
gradient.setColorAt(0.9, QtGui.QColor(127, 127, 127))
|
||||
gradient.setColorAt(1, QtCore.Qt.transparent)
|
||||
|
||||
painter.setPen(QtCore.Qt.NoPen)
|
||||
painter.setBrush(gradient)
|
||||
painter.drawEllipse(handle_rect)
|
||||
|
||||
painter.restore()
|
||||
|
||||
|
||||
class AlphaInputs(QtWidgets.QWidget):
|
||||
alpha_changed = QtCore.Signal(int)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(AlphaInputs, self).__init__(parent)
|
||||
|
||||
self._block_changes = False
|
||||
self.alpha_value = None
|
||||
|
||||
percent_input = QtWidgets.QDoubleSpinBox(self)
|
||||
percent_input.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
percent_input.setMinimum(0)
|
||||
percent_input.setMaximum(100)
|
||||
percent_input.setDecimals(2)
|
||||
|
||||
int_input = QtWidgets.QSpinBox(self)
|
||||
int_input.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
int_input.setMinimum(0)
|
||||
int_input.setMaximum(255)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(int_input)
|
||||
layout.addWidget(QtWidgets.QLabel("0-255"))
|
||||
layout.addWidget(percent_input)
|
||||
layout.addWidget(QtWidgets.QLabel("%"))
|
||||
|
||||
percent_input.valueChanged.connect(self._on_percent_change)
|
||||
int_input.valueChanged.connect(self._on_int_change)
|
||||
|
||||
self.percent_input = percent_input
|
||||
self.int_input = int_input
|
||||
|
||||
self.set_alpha(255)
|
||||
|
||||
def set_alpha(self, alpha):
|
||||
if alpha == self.alpha_value:
|
||||
return
|
||||
self.alpha_value = alpha
|
||||
|
||||
self.update_alpha()
|
||||
|
||||
def _on_percent_change(self):
|
||||
if self._block_changes:
|
||||
return
|
||||
self.alpha_value = int(self.percent_input.value() * 255 / 100)
|
||||
self.alpha_changed.emit(self.alpha_value)
|
||||
self.update_alpha()
|
||||
|
||||
def _on_int_change(self):
|
||||
if self._block_changes:
|
||||
return
|
||||
|
||||
self.alpha_value = self.int_input.value()
|
||||
self.alpha_changed.emit(self.alpha_value)
|
||||
self.update_alpha()
|
||||
|
||||
def update_alpha(self):
|
||||
self._block_changes = True
|
||||
if self.int_input.value() != self.alpha_value:
|
||||
self.int_input.setValue(self.alpha_value)
|
||||
|
||||
percent = round(100 * self.alpha_value / 255, 2)
|
||||
if self.percent_input.value() != percent:
|
||||
self.percent_input.setValue(percent)
|
||||
|
||||
self._block_changes = False
|
||||
|
||||
|
||||
class RGBInputs(QtWidgets.QWidget):
|
||||
value_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, color, parent=None):
|
||||
super(RGBInputs, self).__init__(parent)
|
||||
|
||||
self._block_changes = False
|
||||
|
||||
self.color = color
|
||||
|
||||
input_red = QtWidgets.QSpinBox(self)
|
||||
input_green = QtWidgets.QSpinBox(self)
|
||||
input_blue = QtWidgets.QSpinBox(self)
|
||||
|
||||
input_red.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
input_green.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
input_blue.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
|
||||
input_red.setMinimum(0)
|
||||
input_green.setMinimum(0)
|
||||
input_blue.setMinimum(0)
|
||||
|
||||
input_red.setMaximum(255)
|
||||
input_green.setMaximum(255)
|
||||
input_blue.setMaximum(255)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(input_red, 1)
|
||||
layout.addWidget(input_green, 1)
|
||||
layout.addWidget(input_blue, 1)
|
||||
|
||||
input_red.valueChanged.connect(self._on_red_change)
|
||||
input_green.valueChanged.connect(self._on_green_change)
|
||||
input_blue.valueChanged.connect(self._on_blue_change)
|
||||
|
||||
self.input_red = input_red
|
||||
self.input_green = input_green
|
||||
self.input_blue = input_blue
|
||||
|
||||
def _on_red_change(self, value):
|
||||
if self._block_changes:
|
||||
return
|
||||
self.color.setRed(value)
|
||||
self._on_change()
|
||||
|
||||
def _on_green_change(self, value):
|
||||
if self._block_changes:
|
||||
return
|
||||
self.color.setGreen(value)
|
||||
self._on_change()
|
||||
|
||||
def _on_blue_change(self, value):
|
||||
if self._block_changes:
|
||||
return
|
||||
self.color.setBlue(value)
|
||||
self._on_change()
|
||||
|
||||
def _on_change(self):
|
||||
self.value_changed.emit()
|
||||
|
||||
def color_changed(self):
|
||||
if (
|
||||
self.input_red.value() == self.color.red()
|
||||
and self.input_green.value() == self.color.green()
|
||||
and self.input_blue.value() == self.color.blue()
|
||||
):
|
||||
return
|
||||
|
||||
self._block_changes = True
|
||||
|
||||
self.input_red.setValue(self.color.red())
|
||||
self.input_green.setValue(self.color.green())
|
||||
self.input_blue.setValue(self.color.blue())
|
||||
|
||||
self._block_changes = False
|
||||
|
||||
|
||||
class CMYKInputs(QtWidgets.QWidget):
|
||||
value_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, color, parent=None):
|
||||
super(CMYKInputs, self).__init__(parent)
|
||||
|
||||
self.color = color
|
||||
|
||||
self._block_changes = False
|
||||
|
||||
input_cyan = QtWidgets.QSpinBox(self)
|
||||
input_magenta = QtWidgets.QSpinBox(self)
|
||||
input_yellow = QtWidgets.QSpinBox(self)
|
||||
input_black = QtWidgets.QSpinBox(self)
|
||||
|
||||
input_cyan.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
input_magenta.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
input_yellow.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
input_black.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
|
||||
input_cyan.setMinimum(0)
|
||||
input_magenta.setMinimum(0)
|
||||
input_yellow.setMinimum(0)
|
||||
input_black.setMinimum(0)
|
||||
|
||||
input_cyan.setMaximum(255)
|
||||
input_magenta.setMaximum(255)
|
||||
input_yellow.setMaximum(255)
|
||||
input_black.setMaximum(255)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(input_cyan, 1)
|
||||
layout.addWidget(input_magenta, 1)
|
||||
layout.addWidget(input_yellow, 1)
|
||||
layout.addWidget(input_black, 1)
|
||||
|
||||
input_cyan.valueChanged.connect(self._on_change)
|
||||
input_magenta.valueChanged.connect(self._on_change)
|
||||
input_yellow.valueChanged.connect(self._on_change)
|
||||
input_black.valueChanged.connect(self._on_change)
|
||||
|
||||
self.input_cyan = input_cyan
|
||||
self.input_magenta = input_magenta
|
||||
self.input_yellow = input_yellow
|
||||
self.input_black = input_black
|
||||
|
||||
def _on_change(self):
|
||||
if self._block_changes:
|
||||
return
|
||||
self.color.setCmyk(
|
||||
self.input_cyan.value(),
|
||||
self.input_magenta.value(),
|
||||
self.input_yellow.value(),
|
||||
self.input_black.value()
|
||||
)
|
||||
self.value_changed.emit()
|
||||
|
||||
def color_changed(self):
|
||||
if self._block_changes:
|
||||
return
|
||||
_cur_color = QtGui.QColor()
|
||||
_cur_color.setCmyk(
|
||||
self.input_cyan.value(),
|
||||
self.input_magenta.value(),
|
||||
self.input_yellow.value(),
|
||||
self.input_black.value()
|
||||
)
|
||||
if (
|
||||
_cur_color.red() == self.color.red()
|
||||
and _cur_color.green() == self.color.green()
|
||||
and _cur_color.blue() == self.color.blue()
|
||||
):
|
||||
return
|
||||
|
||||
c, m, y, k, _ = self.color.getCmyk()
|
||||
self._block_changes = True
|
||||
|
||||
self.input_cyan.setValue(c)
|
||||
self.input_magenta.setValue(m)
|
||||
self.input_yellow.setValue(y)
|
||||
self.input_black.setValue(k)
|
||||
|
||||
self._block_changes = False
|
||||
|
||||
|
||||
class HEXInputs(QtWidgets.QWidget):
|
||||
hex_regex = re.compile("^#(([0-9a-fA-F]{2}){3}|([0-9a-fA-F]){3})$")
|
||||
value_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, color, parent=None):
|
||||
super(HEXInputs, self).__init__(parent)
|
||||
self.color = color
|
||||
|
||||
input_field = QtWidgets.QLineEdit(self)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(input_field, 1)
|
||||
|
||||
input_field.textChanged.connect(self._on_change)
|
||||
|
||||
self.input_field = input_field
|
||||
|
||||
def _on_change(self):
|
||||
if self._block_changes:
|
||||
return
|
||||
input_value = self.input_field.text()
|
||||
# TODO what if does not match?
|
||||
if self.hex_regex.match(input_value):
|
||||
self.color.setNamedColor(input_value)
|
||||
self.value_changed.emit()
|
||||
|
||||
def color_changed(self):
|
||||
input_value = self.input_field.text()
|
||||
if self.hex_regex.match(input_value):
|
||||
_cur_color = QtGui.QColor()
|
||||
_cur_color.setNamedColor(input_value)
|
||||
if (
|
||||
_cur_color.red() == self.color.red()
|
||||
and _cur_color.green() == self.color.green()
|
||||
and _cur_color.blue() == self.color.blue()
|
||||
):
|
||||
return
|
||||
self._block_changes = True
|
||||
|
||||
self.input_field.setText(self.color.name())
|
||||
|
||||
self._block_changes = False
|
||||
|
||||
|
||||
class HSVInputs(QtWidgets.QWidget):
|
||||
value_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, color, parent=None):
|
||||
super(HSVInputs, self).__init__(parent)
|
||||
|
||||
self._block_changes = False
|
||||
|
||||
self.color = color
|
||||
|
||||
input_hue = QtWidgets.QSpinBox(self)
|
||||
input_sat = QtWidgets.QSpinBox(self)
|
||||
input_val = QtWidgets.QSpinBox(self)
|
||||
|
||||
input_hue.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
input_sat.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
input_val.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
|
||||
input_hue.setMinimum(0)
|
||||
input_sat.setMinimum(0)
|
||||
input_val.setMinimum(0)
|
||||
|
||||
input_hue.setMaximum(359)
|
||||
input_sat.setMaximum(255)
|
||||
input_val.setMaximum(255)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(input_hue, 1)
|
||||
layout.addWidget(input_sat, 1)
|
||||
layout.addWidget(input_val, 1)
|
||||
|
||||
input_hue.valueChanged.connect(self._on_change)
|
||||
input_sat.valueChanged.connect(self._on_change)
|
||||
input_val.valueChanged.connect(self._on_change)
|
||||
|
||||
self.input_hue = input_hue
|
||||
self.input_sat = input_sat
|
||||
self.input_val = input_val
|
||||
|
||||
def _on_change(self):
|
||||
if self._block_changes:
|
||||
return
|
||||
self.color.setHsv(
|
||||
self.input_hue.value(),
|
||||
self.input_sat.value(),
|
||||
self.input_val.value()
|
||||
)
|
||||
self.value_changed.emit()
|
||||
|
||||
def color_changed(self):
|
||||
_cur_color = QtGui.QColor()
|
||||
_cur_color.setHsv(
|
||||
self.input_hue.value(),
|
||||
self.input_sat.value(),
|
||||
self.input_val.value()
|
||||
)
|
||||
if (
|
||||
_cur_color.red() == self.color.red()
|
||||
and _cur_color.green() == self.color.green()
|
||||
and _cur_color.blue() == self.color.blue()
|
||||
):
|
||||
return
|
||||
|
||||
self._block_changes = True
|
||||
h, s, v, _ = self.color.getHsv()
|
||||
|
||||
self.input_hue.setValue(h)
|
||||
self.input_sat.setValue(s)
|
||||
self.input_val.setValue(v)
|
||||
|
||||
self._block_changes = False
|
||||
|
||||
|
||||
class HSLInputs(QtWidgets.QWidget):
|
||||
value_changed = QtCore.Signal()
|
||||
|
||||
def __init__(self, color, parent=None):
|
||||
super(HSLInputs, self).__init__(parent)
|
||||
|
||||
self._block_changes = False
|
||||
|
||||
self.color = color
|
||||
|
||||
input_hue = QtWidgets.QSpinBox(self)
|
||||
input_sat = QtWidgets.QSpinBox(self)
|
||||
input_light = QtWidgets.QSpinBox(self)
|
||||
|
||||
input_hue.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
input_sat.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
input_light.setButtonSymbols(QtWidgets.QSpinBox.NoButtons)
|
||||
|
||||
input_hue.setMinimum(0)
|
||||
input_sat.setMinimum(0)
|
||||
input_light.setMinimum(0)
|
||||
|
||||
input_hue.setMaximum(359)
|
||||
input_sat.setMaximum(255)
|
||||
input_light.setMaximum(255)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(input_hue, 1)
|
||||
layout.addWidget(input_sat, 1)
|
||||
layout.addWidget(input_light, 1)
|
||||
|
||||
input_hue.valueChanged.connect(self._on_change)
|
||||
input_sat.valueChanged.connect(self._on_change)
|
||||
input_light.valueChanged.connect(self._on_change)
|
||||
|
||||
self.input_hue = input_hue
|
||||
self.input_sat = input_sat
|
||||
self.input_light = input_light
|
||||
|
||||
def _on_change(self):
|
||||
if self._block_changes:
|
||||
return
|
||||
self.color.setHsl(
|
||||
self.input_hue.value(),
|
||||
self.input_sat.value(),
|
||||
self.input_light.value()
|
||||
)
|
||||
self.value_changed.emit()
|
||||
|
||||
def color_changed(self):
|
||||
_cur_color = QtGui.QColor()
|
||||
_cur_color.setHsl(
|
||||
self.input_hue.value(),
|
||||
self.input_sat.value(),
|
||||
self.input_light.value()
|
||||
)
|
||||
if (
|
||||
_cur_color.red() == self.color.red()
|
||||
and _cur_color.green() == self.color.green()
|
||||
and _cur_color.blue() == self.color.blue()
|
||||
):
|
||||
return
|
||||
|
||||
self._block_changes = True
|
||||
h, s, l, _ = self.color.getHsl()
|
||||
|
||||
self.input_hue.setValue(h)
|
||||
self.input_sat.setValue(s)
|
||||
self.input_light.setValue(l)
|
||||
|
||||
self._block_changes = False
|
||||
|
||||
|
||||
class ColorInputsWidget(QtWidgets.QWidget):
|
||||
color_changed = QtCore.Signal(QtGui.QColor)
|
||||
|
||||
def __init__(self, parent=None, **kwargs):
|
||||
super(ColorInputsWidget, self).__init__(parent)
|
||||
|
||||
color = QtGui.QColor()
|
||||
|
||||
input_fields = []
|
||||
|
||||
if kwargs.get("use_hex", True):
|
||||
input_fields.append(HEXInputs(color, self))
|
||||
|
||||
if kwargs.get("use_rgb", True):
|
||||
input_fields.append(RGBInputs(color, self))
|
||||
|
||||
if kwargs.get("use_hsl", True):
|
||||
input_fields.append(HSLInputs(color, self))
|
||||
|
||||
if kwargs.get("use_hsv", True):
|
||||
input_fields.append(HSVInputs(color, self))
|
||||
|
||||
if kwargs.get("use_cmyk", True):
|
||||
input_fields.append(CMYKInputs(color, self))
|
||||
|
||||
inputs_widget = QtWidgets.QWidget(self)
|
||||
inputs_layout = QtWidgets.QVBoxLayout(inputs_widget)
|
||||
|
||||
for input_field in input_fields:
|
||||
inputs_layout.addWidget(input_field)
|
||||
input_field.value_changed.connect(self._on_value_change)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(inputs_widget, 0)
|
||||
spacer = QtWidgets.QWidget(self)
|
||||
layout.addWidget(spacer, 1)
|
||||
|
||||
self.input_fields = input_fields
|
||||
|
||||
self.color = color
|
||||
|
||||
def set_color(self, color):
|
||||
if (
|
||||
color.red() == self.color.red()
|
||||
and color.green() == self.color.green()
|
||||
and color.blue() == self.color.blue()
|
||||
):
|
||||
return
|
||||
self.color.setRed(color.red())
|
||||
self.color.setGreen(color.green())
|
||||
self.color.setBlue(color.blue())
|
||||
self._on_value_change()
|
||||
|
||||
def _on_value_change(self):
|
||||
for input_field in self.input_fields:
|
||||
input_field.color_changed()
|
||||
self.color_changed.emit(self.color)
|
||||
176
openpype/widgets/color_widgets/color_picker_widget.py
Normal file
|
|
@ -0,0 +1,176 @@
|
|||
import os
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
from .color_triangle import QtColorTriangle
|
||||
from .color_view import ColorViewer
|
||||
from .color_screen_pick import PickScreenColorWidget
|
||||
from .color_inputs import (
|
||||
AlphaSlider,
|
||||
AlphaInputs,
|
||||
HEXInputs,
|
||||
RGBInputs,
|
||||
HSLInputs,
|
||||
HSVInputs
|
||||
)
|
||||
|
||||
|
||||
class ColorPickerWidget(QtWidgets.QWidget):
|
||||
color_changed = QtCore.Signal(QtGui.QColor)
|
||||
|
||||
def __init__(self, color=None, parent=None):
|
||||
super(ColorPickerWidget, self).__init__(parent)
|
||||
|
||||
# Color triangle
|
||||
color_triangle = QtColorTriangle(self)
|
||||
|
||||
alpha_slider_proxy = QtWidgets.QWidget(self)
|
||||
alpha_slider = AlphaSlider(QtCore.Qt.Horizontal, alpha_slider_proxy)
|
||||
|
||||
alpha_slider_layout = QtWidgets.QHBoxLayout(alpha_slider_proxy)
|
||||
alpha_slider_layout.setContentsMargins(5, 5, 5, 5)
|
||||
alpha_slider_layout.addWidget(alpha_slider, 1)
|
||||
|
||||
# Eye picked widget
|
||||
pick_widget = PickScreenColorWidget()
|
||||
pick_widget.setMaximumHeight(50)
|
||||
|
||||
# Color pick button
|
||||
btn_pick_color = QtWidgets.QPushButton(self)
|
||||
icon_path = os.path.join(
|
||||
os.path.dirname(os.path.abspath(__file__)),
|
||||
"eyedropper.png"
|
||||
)
|
||||
btn_pick_color.setIcon(QtGui.QIcon(icon_path))
|
||||
btn_pick_color.setToolTip("Pick a color")
|
||||
|
||||
# Color preview
|
||||
color_view = ColorViewer(self)
|
||||
color_view.setMaximumHeight(50)
|
||||
|
||||
alpha_inputs = AlphaInputs(self)
|
||||
|
||||
color_inputs_color = QtGui.QColor()
|
||||
col_inputs_by_label = [
|
||||
("HEX", HEXInputs(color_inputs_color, self)),
|
||||
("RGB", RGBInputs(color_inputs_color, self)),
|
||||
("HSL", HSLInputs(color_inputs_color, self)),
|
||||
("HSV", HSVInputs(color_inputs_color, self))
|
||||
]
|
||||
|
||||
layout = QtWidgets.QGridLayout(self)
|
||||
empty_col = 1
|
||||
label_col = empty_col + 1
|
||||
input_col = label_col + 1
|
||||
empty_widget = QtWidgets.QWidget(self)
|
||||
empty_widget.setFixedWidth(10)
|
||||
layout.addWidget(empty_widget, 0, empty_col)
|
||||
|
||||
row = 0
|
||||
layout.addWidget(btn_pick_color, row, label_col)
|
||||
layout.addWidget(color_view, row, input_col)
|
||||
row += 1
|
||||
|
||||
color_input_fields = []
|
||||
for label, input_field in col_inputs_by_label:
|
||||
layout.addWidget(QtWidgets.QLabel(label, self), row, label_col)
|
||||
layout.addWidget(input_field, row, input_col)
|
||||
input_field.value_changed.connect(
|
||||
self._on_color_input_value_change
|
||||
)
|
||||
color_input_fields.append(input_field)
|
||||
row += 1
|
||||
|
||||
layout.addWidget(color_triangle, 0, 0, row + 1, 1)
|
||||
layout.setRowStretch(row, 1)
|
||||
row += 1
|
||||
|
||||
layout.addWidget(alpha_slider_proxy, row, 0)
|
||||
|
||||
layout.addWidget(QtWidgets.QLabel("Alpha", self), row, label_col)
|
||||
layout.addWidget(alpha_inputs, row, input_col)
|
||||
row += 1
|
||||
layout.setRowStretch(row, 1)
|
||||
|
||||
color_view.set_color(color_triangle.cur_color)
|
||||
|
||||
color_triangle.color_changed.connect(self.triangle_color_changed)
|
||||
alpha_slider.valueChanged.connect(self._on_alpha_slider_change)
|
||||
pick_widget.color_selected.connect(self.on_color_change)
|
||||
alpha_inputs.alpha_changed.connect(self._on_alpha_inputs_changed)
|
||||
btn_pick_color.released.connect(self.pick_color)
|
||||
|
||||
self.color_input_fields = color_input_fields
|
||||
self.color_inputs_color = color_inputs_color
|
||||
|
||||
self.pick_widget = pick_widget
|
||||
|
||||
self.color_triangle = color_triangle
|
||||
self.alpha_slider = alpha_slider
|
||||
|
||||
self.color_view = color_view
|
||||
self.alpha_inputs = alpha_inputs
|
||||
self.btn_pick_color = btn_pick_color
|
||||
|
||||
self._minimum_size_set = False
|
||||
|
||||
if color:
|
||||
self.set_color(color)
|
||||
self.alpha_changed(color.alpha())
|
||||
|
||||
def showEvent(self, event):
|
||||
super(ColorPickerWidget, self).showEvent(event)
|
||||
if self._minimum_size_set:
|
||||
return
|
||||
|
||||
triangle_size = max(int(self.width() / 5 * 3), 180)
|
||||
self.color_triangle.setMinimumWidth(triangle_size)
|
||||
self.color_triangle.setMinimumHeight(triangle_size)
|
||||
self._minimum_size_set = True
|
||||
|
||||
def color(self):
|
||||
return self.color_view.color()
|
||||
|
||||
def set_color(self, color):
|
||||
self.alpha_inputs.set_alpha(color.alpha())
|
||||
self.on_color_change(color)
|
||||
|
||||
def pick_color(self):
|
||||
self.pick_widget.pick_color()
|
||||
|
||||
def triangle_color_changed(self, color):
|
||||
self.color_view.set_color(color)
|
||||
if self.color_inputs_color != color:
|
||||
self.color_inputs_color.setRgb(
|
||||
color.red(), color.green(), color.blue()
|
||||
)
|
||||
for color_input in self.color_input_fields:
|
||||
color_input.color_changed()
|
||||
|
||||
def on_color_change(self, color):
|
||||
self.color_view.set_color(color)
|
||||
self.color_triangle.set_color(color)
|
||||
if self.color_inputs_color != color:
|
||||
self.color_inputs_color.setRgb(
|
||||
color.red(), color.green(), color.blue()
|
||||
)
|
||||
for color_input in self.color_input_fields:
|
||||
color_input.color_changed()
|
||||
|
||||
def _on_color_input_value_change(self):
|
||||
for input_field in self.color_input_fields:
|
||||
input_field.color_changed()
|
||||
self.on_color_change(QtGui.QColor(self.color_inputs_color))
|
||||
|
||||
def alpha_changed(self, value):
|
||||
self.color_view.set_alpha(value)
|
||||
if self.alpha_slider.value() != value:
|
||||
self.alpha_slider.setValue(value)
|
||||
|
||||
if self.alpha_inputs.alpha_value != value:
|
||||
self.alpha_inputs.set_alpha(value)
|
||||
|
||||
def _on_alpha_inputs_changed(self, value):
|
||||
self.alpha_changed(value)
|
||||
|
||||
def _on_alpha_slider_change(self, value):
|
||||
self.alpha_changed(value)
|
||||
248
openpype/widgets/color_widgets/color_screen_pick.py
Normal file
|
|
@ -0,0 +1,248 @@
|
|||
import Qt
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
|
||||
class PickScreenColorWidget(QtWidgets.QWidget):
|
||||
color_selected = QtCore.Signal(QtGui.QColor)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(PickScreenColorWidget, self).__init__(parent)
|
||||
self.labels = []
|
||||
self.magnification = 2
|
||||
|
||||
self._min_magnification = 1
|
||||
self._max_magnification = 10
|
||||
|
||||
def add_magnification_delta(self, delta):
|
||||
_delta = abs(delta / 1000)
|
||||
if delta > 0:
|
||||
self.magnification += _delta
|
||||
else:
|
||||
self.magnification -= _delta
|
||||
|
||||
if self.magnification > self._max_magnification:
|
||||
self.magnification = self._max_magnification
|
||||
elif self.magnification < self._min_magnification:
|
||||
self.magnification = self._min_magnification
|
||||
|
||||
def pick_color(self):
|
||||
if self.labels:
|
||||
if self.labels[0].isVisible():
|
||||
return
|
||||
self.labels = []
|
||||
|
||||
for screen in QtWidgets.QApplication.screens():
|
||||
label = PickLabel(self)
|
||||
label.pick_color(screen)
|
||||
label.color_selected.connect(self.on_color_select)
|
||||
label.close_session.connect(self.end_pick_session)
|
||||
self.labels.append(label)
|
||||
|
||||
def end_pick_session(self):
|
||||
for label in self.labels:
|
||||
label.close()
|
||||
self.labels = []
|
||||
|
||||
def on_color_select(self, color):
|
||||
self.color_selected.emit(color)
|
||||
self.end_pick_session()
|
||||
|
||||
|
||||
class PickLabel(QtWidgets.QLabel):
|
||||
color_selected = QtCore.Signal(QtGui.QColor)
|
||||
close_session = QtCore.Signal()
|
||||
|
||||
def __init__(self, pick_widget):
|
||||
super(PickLabel, self).__init__()
|
||||
self.setMouseTracking(True)
|
||||
|
||||
self.pick_widget = pick_widget
|
||||
|
||||
self.radius_pen = QtGui.QPen(QtGui.QColor(27, 27, 27), 2)
|
||||
self.text_pen = QtGui.QPen(QtGui.QColor(127, 127, 127), 4)
|
||||
self.text_bg = QtGui.QBrush(QtGui.QColor(27, 27, 27))
|
||||
self._mouse_over = False
|
||||
|
||||
self.radius = 100
|
||||
self.radius_ratio = 11
|
||||
|
||||
@property
|
||||
def magnification(self):
|
||||
return self.pick_widget.magnification
|
||||
|
||||
def pick_color(self, screen_obj):
|
||||
self.show()
|
||||
self.windowHandle().setScreen(screen_obj)
|
||||
geo = screen_obj.geometry()
|
||||
args = (
|
||||
QtWidgets.QApplication.desktop().winId(),
|
||||
geo.x(), geo.y(), geo.width(), geo.height()
|
||||
)
|
||||
if Qt.__binding__ in ("PyQt4", "PySide"):
|
||||
pix = QtGui.QPixmap.grabWindow(*args)
|
||||
else:
|
||||
pix = screen_obj.grabWindow(*args)
|
||||
|
||||
if pix.width() > pix.height():
|
||||
size = pix.height()
|
||||
else:
|
||||
size = pix.width()
|
||||
|
||||
self.radius = int(size / self.radius_ratio)
|
||||
|
||||
self.setPixmap(pix)
|
||||
self.showFullScreen()
|
||||
|
||||
def wheelEvent(self, event):
|
||||
y_delta = event.angleDelta().y()
|
||||
self.pick_widget.add_magnification_delta(y_delta)
|
||||
self.update()
|
||||
|
||||
def enterEvent(self, event):
|
||||
self._mouse_over = True
|
||||
super().enterEvent(event)
|
||||
|
||||
def leaveEvent(self, event):
|
||||
self._mouse_over = False
|
||||
super().leaveEvent(event)
|
||||
self.update()
|
||||
|
||||
def mouseMoveEvent(self, event):
|
||||
self.update()
|
||||
|
||||
def paintEvent(self, event):
|
||||
super().paintEvent(event)
|
||||
if not self._mouse_over:
|
||||
return
|
||||
|
||||
mouse_pos_to_widet = self.mapFromGlobal(QtGui.QCursor.pos())
|
||||
|
||||
magnified_half_size = self.radius / self.magnification
|
||||
magnified_size = magnified_half_size * 2
|
||||
|
||||
zoom_x_1 = mouse_pos_to_widet.x() - magnified_half_size
|
||||
zoom_x_2 = mouse_pos_to_widet.x() + magnified_half_size
|
||||
zoom_y_1 = mouse_pos_to_widet.y() - magnified_half_size
|
||||
zoom_y_2 = mouse_pos_to_widet.y() + magnified_half_size
|
||||
pix_width = magnified_size
|
||||
pix_height = magnified_size
|
||||
draw_pos_x = 0
|
||||
draw_pos_y = 0
|
||||
if zoom_x_1 < 0:
|
||||
draw_pos_x = abs(zoom_x_1)
|
||||
pix_width -= draw_pos_x
|
||||
zoom_x_1 = 1
|
||||
elif zoom_x_2 > self.pixmap().width():
|
||||
pix_width -= zoom_x_2 - self.pixmap().width()
|
||||
|
||||
if zoom_y_1 < 0:
|
||||
draw_pos_y = abs(zoom_y_1)
|
||||
pix_height -= draw_pos_y
|
||||
zoom_y_1 = 1
|
||||
elif zoom_y_2 > self.pixmap().height():
|
||||
pix_height -= zoom_y_2 - self.pixmap().height()
|
||||
|
||||
new_pix = QtGui.QPixmap(magnified_size, magnified_size)
|
||||
new_pix.fill(QtCore.Qt.transparent)
|
||||
new_pix_painter = QtGui.QPainter(new_pix)
|
||||
new_pix_painter.drawPixmap(
|
||||
QtCore.QRect(draw_pos_x, draw_pos_y, pix_width, pix_height),
|
||||
self.pixmap().copy(zoom_x_1, zoom_y_1, pix_width, pix_height)
|
||||
)
|
||||
new_pix_painter.end()
|
||||
|
||||
painter = QtGui.QPainter(self)
|
||||
|
||||
ellipse_rect = QtCore.QRect(
|
||||
mouse_pos_to_widet.x() - self.radius,
|
||||
mouse_pos_to_widet.y() - self.radius,
|
||||
self.radius * 2,
|
||||
self.radius * 2
|
||||
)
|
||||
ellipse_rect_f = QtCore.QRectF(ellipse_rect)
|
||||
path = QtGui.QPainterPath()
|
||||
path.addEllipse(ellipse_rect_f)
|
||||
painter.setClipPath(path)
|
||||
|
||||
new_pix_rect = QtCore.QRect(
|
||||
mouse_pos_to_widet.x() - self.radius + 1,
|
||||
mouse_pos_to_widet.y() - self.radius + 1,
|
||||
new_pix.width() * self.magnification,
|
||||
new_pix.height() * self.magnification
|
||||
)
|
||||
|
||||
painter.drawPixmap(new_pix_rect, new_pix)
|
||||
|
||||
painter.setClipping(False)
|
||||
|
||||
painter.setRenderHint(QtGui.QPainter.Antialiasing)
|
||||
|
||||
painter.setPen(self.radius_pen)
|
||||
painter.drawEllipse(ellipse_rect_f)
|
||||
|
||||
image = self.pixmap().toImage()
|
||||
if image.valid(mouse_pos_to_widet):
|
||||
color = QtGui.QColor(image.pixel(mouse_pos_to_widet))
|
||||
else:
|
||||
color = QtGui.QColor()
|
||||
|
||||
color_text = "Red: {} - Green: {} - Blue: {}".format(
|
||||
color.red(), color.green(), color.blue()
|
||||
)
|
||||
font = painter.font()
|
||||
font.setPointSize(self.radius / 10)
|
||||
painter.setFont(font)
|
||||
|
||||
text_rect_height = int(painter.fontMetrics().height() + 10)
|
||||
text_rect = QtCore.QRect(
|
||||
ellipse_rect.x(),
|
||||
ellipse_rect.bottom(),
|
||||
ellipse_rect.width(),
|
||||
text_rect_height
|
||||
)
|
||||
if text_rect.bottom() > self.pixmap().height():
|
||||
text_rect.moveBottomLeft(ellipse_rect.topLeft())
|
||||
|
||||
rect_radius = text_rect_height / 2
|
||||
path = QtGui.QPainterPath()
|
||||
path.addRoundedRect(
|
||||
QtCore.QRectF(text_rect),
|
||||
rect_radius,
|
||||
rect_radius
|
||||
)
|
||||
painter.fillPath(path, self.text_bg)
|
||||
|
||||
painter.setPen(self.text_pen)
|
||||
painter.drawText(
|
||||
text_rect,
|
||||
QtCore.Qt.AlignLeft | QtCore.Qt.AlignCenter,
|
||||
color_text
|
||||
)
|
||||
|
||||
color_rect_x = ellipse_rect.x() - text_rect_height
|
||||
if color_rect_x < 0:
|
||||
color_rect_x += (text_rect_height + ellipse_rect.width())
|
||||
|
||||
color_rect = QtCore.QRect(
|
||||
color_rect_x,
|
||||
ellipse_rect.y(),
|
||||
text_rect_height,
|
||||
ellipse_rect.height()
|
||||
)
|
||||
path = QtGui.QPainterPath()
|
||||
path.addRoundedRect(
|
||||
QtCore.QRectF(color_rect),
|
||||
rect_radius,
|
||||
rect_radius
|
||||
)
|
||||
painter.fillPath(path, color)
|
||||
painter.drawRoundedRect(color_rect, rect_radius, rect_radius)
|
||||
painter.end()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
color = QtGui.QColor(self.pixmap().toImage().pixel(event.pos()))
|
||||
self.color_selected.emit(color)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == QtCore.Qt.Key_Escape:
|
||||
self.close_session.emit()
|
||||
1431
openpype/widgets/color_widgets/color_triangle.py
Normal file
83
openpype/widgets/color_widgets/color_view.py
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
|
||||
def draw_checkerboard_tile(piece_size=None, color_1=None, color_2=None):
|
||||
if piece_size is None:
|
||||
piece_size = 7
|
||||
|
||||
if color_1 is None:
|
||||
color_1 = QtGui.QColor(188, 188, 188)
|
||||
|
||||
if color_2 is None:
|
||||
color_2 = QtGui.QColor(90, 90, 90)
|
||||
|
||||
pix = QtGui.QPixmap(piece_size * 2, piece_size * 2)
|
||||
pix_painter = QtGui.QPainter(pix)
|
||||
|
||||
rect = QtCore.QRect(
|
||||
0, 0, piece_size, piece_size
|
||||
)
|
||||
pix_painter.fillRect(rect, color_1)
|
||||
rect.moveTo(piece_size, piece_size)
|
||||
pix_painter.fillRect(rect, color_1)
|
||||
rect.moveTo(piece_size, 0)
|
||||
pix_painter.fillRect(rect, color_2)
|
||||
rect.moveTo(0, piece_size)
|
||||
pix_painter.fillRect(rect, color_2)
|
||||
pix_painter.end()
|
||||
|
||||
return pix
|
||||
|
||||
|
||||
class ColorViewer(QtWidgets.QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super(ColorViewer, self).__init__(parent)
|
||||
|
||||
self.setMinimumSize(10, 10)
|
||||
|
||||
self.alpha = 255
|
||||
self.actual_pen = QtGui.QPen()
|
||||
self.actual_color = QtGui.QColor()
|
||||
self._checkerboard = None
|
||||
|
||||
def checkerboard(self):
|
||||
if not self._checkerboard:
|
||||
self._checkerboard = draw_checkerboard_tile(4)
|
||||
return self._checkerboard
|
||||
|
||||
def color(self):
|
||||
return self.actual_color
|
||||
|
||||
def set_color(self, color):
|
||||
if color == self.actual_color:
|
||||
return
|
||||
|
||||
# Create copy of entered color
|
||||
self.actual_color = QtGui.QColor(color)
|
||||
# Set alpha by current alpha value
|
||||
self.actual_color.setAlpha(self.alpha)
|
||||
# Repaint
|
||||
self.update()
|
||||
|
||||
def set_alpha(self, alpha):
|
||||
if alpha == self.alpha:
|
||||
return
|
||||
# Change alpha of current color
|
||||
self.actual_color.setAlpha(alpha)
|
||||
# Store the value
|
||||
self.alpha = alpha
|
||||
# Repaint
|
||||
self.update()
|
||||
|
||||
def paintEvent(self, event):
|
||||
clip_rect = event.rect()
|
||||
rect = clip_rect.adjusted(0, 0, -1, -1)
|
||||
|
||||
painter = QtGui.QPainter(self)
|
||||
painter.setClipRect(clip_rect)
|
||||
painter.drawTiledPixmap(rect, self.checkerboard())
|
||||
painter.setBrush(self.actual_color)
|
||||
pen = QtGui.QPen(QtGui.QColor(255, 255, 255, 67))
|
||||
painter.setPen(pen)
|
||||
painter.drawRect(rect)
|
||||
painter.end()
|
||||
BIN
openpype/widgets/color_widgets/eyedropper.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
|
|
@ -1,6 +1,6 @@
|
|||
[tool.poetry]
|
||||
name = "OpenPype"
|
||||
version = "3.0.0-rc4"
|
||||
version = "3.0.0-rc.5"
|
||||
description = "Open VFX and Animation pipeline with support."
|
||||
authors = ["OpenPype Team <info@openpype.io>"]
|
||||
license = "MIT License"
|
||||
|
|
|
|||
2
setup.py
|
|
@ -98,7 +98,7 @@ executables = [
|
|||
setup(
|
||||
name="OpenPype",
|
||||
version=__version__,
|
||||
description="Ultimate pipeline",
|
||||
description="OpenPype",
|
||||
cmdclass={"build_sphinx": BuildDoc},
|
||||
options={
|
||||
"build_exe": build_exe_options,
|
||||
|
|
|
|||
|
|
@ -12,8 +12,22 @@
|
|||
|
||||
PS> .\build.ps1
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
To build without automatical submodule update:
|
||||
PS> .\build.ps1 --no-submodule-update
|
||||
|
||||
.LINK
|
||||
https://openpype.io/docs
|
||||
|
||||
#>
|
||||
|
||||
$arguments=$ARGS
|
||||
$disable_submodule_update=""
|
||||
if($arguments -eq "--no-submodule-update") {
|
||||
$disable_submodule_update=$true
|
||||
}
|
||||
|
||||
function Start-Progress {
|
||||
param([ScriptBlock]$code)
|
||||
$scroll = "/-\|/-\|"
|
||||
|
|
@ -76,17 +90,17 @@ $art = @"
|
|||
|
||||
. . .. . ..
|
||||
_oOOP3OPP3Op_. .
|
||||
.PPpo~· ·· ~2p. ·· ···· · ·
|
||||
·Ppo · .pPO3Op.· · O:· · · ·
|
||||
.3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · ·
|
||||
·~OP 3PO· .Op3 : · ·· _____ _____ _____
|
||||
·P3O · oP3oP3O3P' · · · · / /·/ /·/ /
|
||||
O3:· O3p~ · ·:· · ·/____/·/____/ /____/
|
||||
'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · ·
|
||||
· ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · ·
|
||||
· '_ .. · . _OP3·· · ·https://openpype.io·· ·
|
||||
~P3·OPPPO3OP~ · ·· ·
|
||||
· ' '· · ·· · · · ·· ·
|
||||
.PPpo~. .. ~2p. .. .... . .
|
||||
.Ppo . .pPO3Op.. . O:. . . .
|
||||
.3Pp . oP3'. 'P33. . 4 .. . . . .. . . .
|
||||
.~OP 3PO. .Op3 : . .. _____ _____ _____
|
||||
.P3O . oP3oP3O3P' . . . . / /./ /./ /
|
||||
O3:. O3p~ . .:. . ./____/./____/ /____/
|
||||
'P . 3p3. oP3~. ..P:. . . .. . . .. . . .
|
||||
. ': . Po' .Opo'. .3O. . o[ by Pype Club ]]]==- - - . .
|
||||
. '_ .. . . _OP3.. . .https://openpype.io.. .
|
||||
~P3.OPPPO3OP~ . .. .
|
||||
. ' '. . .. . . . .. .
|
||||
|
||||
"@
|
||||
|
||||
|
|
@ -134,10 +148,14 @@ catch {
|
|||
Write-Host $_.Exception.Message
|
||||
Exit-WithCode 1
|
||||
}
|
||||
|
||||
Write-Host ">>> " -NoNewLine -ForegroundColor green
|
||||
Write-Host "Making sure submodules are up-to-date ..."
|
||||
git submodule update --init --recursive
|
||||
if (-not $disable_submodule_update) {
|
||||
Write-Host ">>> " -NoNewLine -ForegroundColor green
|
||||
Write-Host "Making sure submodules are up-to-date ..."
|
||||
git submodule update --init --recursive
|
||||
} else {
|
||||
Write-Host "*** " -NoNewLine -ForegroundColor yellow
|
||||
Write-Host "Not updating submodules ..."
|
||||
}
|
||||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor green
|
||||
Write-Host "OpenPype [ " -NoNewline -ForegroundColor white
|
||||
|
|
@ -164,7 +182,7 @@ Write-Host "OK" -ForegroundColor green
|
|||
|
||||
Write-Host ">>> " -NoNewline -ForegroundColor green
|
||||
Write-Host "Building OpenPype ..."
|
||||
$startTime = (Get-Date).Millisecond
|
||||
$startTime = [int][double]::Parse((Get-Date -UFormat %s))
|
||||
|
||||
$out = & poetry run python setup.py build 2>&1
|
||||
if ($LASTEXITCODE -ne 0)
|
||||
|
|
@ -183,7 +201,7 @@ Write-Host ">>> " -NoNewline -ForegroundColor green
|
|||
Write-Host "restoring current directory"
|
||||
Set-Location -Path $current_dir
|
||||
|
||||
$endTime = (Get-Date).Millisecond
|
||||
$endTime = [int][double]::Parse((Get-Date -UFormat %s))
|
||||
Write-Host "*** " -NoNewline -ForegroundColor Cyan
|
||||
Write-Host "All done in $($endTime - $startTime) secs. You will find OpenPype and build log in " -NoNewLine
|
||||
Write-Host "'.\build'" -NoNewline -ForegroundColor Green
|
||||
|
|
|
|||
|
|
@ -57,6 +57,26 @@ BIPurple='\033[1;95m' # Purple
|
|||
BICyan='\033[1;96m' # Cyan
|
||||
BIWhite='\033[1;97m' # White
|
||||
|
||||
args=$@
|
||||
disable_submodule_update = 0
|
||||
while :; do
|
||||
case $1 in
|
||||
--no-submodule-update)
|
||||
disable_submodule_update=1
|
||||
;;
|
||||
--)
|
||||
shift
|
||||
break
|
||||
;;
|
||||
*)
|
||||
break
|
||||
esac
|
||||
|
||||
shift
|
||||
done
|
||||
|
||||
|
||||
|
||||
|
||||
##############################################################################
|
||||
# Detect required version of python
|
||||
|
|
@ -172,9 +192,12 @@ main () {
|
|||
. "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; }
|
||||
fi
|
||||
|
||||
if [ "$disable_submodule_update" == 1 ]; then
|
||||
echo -e "${BIGreen}>>>${RST} Making sure submodules are up-to-date ..."
|
||||
git submodule update --init --recursive
|
||||
|
||||
else
|
||||
echo -e "${BIYellow}***${RST} Not updating submodules ..."
|
||||
fi
|
||||
echo -e "${BIGreen}>>>${RST} Building ..."
|
||||
if [[ "$OSTYPE" == "linux-gnu"* ]]; then
|
||||
poetry run python "$openpype_root/setup.py" build > "$openpype_root/build/build.log" || { echo -e "${BIRed}!!!${RST} Build failed, see the build log."; return; }
|
||||
|
|
|
|||
|
|
@ -26,10 +26,12 @@ import platform
|
|||
from pathlib import Path
|
||||
import shutil
|
||||
import blessed
|
||||
import enlighten
|
||||
import time
|
||||
|
||||
|
||||
term = blessed.Terminal()
|
||||
manager = enlighten.get_manager()
|
||||
|
||||
|
||||
def _print(msg: str, type: int = 0) -> None:
|
||||
|
|
@ -52,6 +54,24 @@ def _print(msg: str, type: int = 0) -> None:
|
|||
print("{}{}".format(header, msg))
|
||||
|
||||
|
||||
def count_folders(path: Path) -> int:
|
||||
"""Recursively count items inside given Path.
|
||||
|
||||
Args:
|
||||
path (Path): Path to count.
|
||||
|
||||
Returns:
|
||||
int: number of items.
|
||||
|
||||
"""
|
||||
cnt = 0
|
||||
for child in path.iterdir():
|
||||
if child.is_dir():
|
||||
cnt += 1
|
||||
cnt += count_folders(child)
|
||||
return cnt
|
||||
|
||||
|
||||
_print("Starting dependency cleanup ...")
|
||||
start_time = time.time_ns()
|
||||
|
||||
|
|
@ -96,30 +116,55 @@ deps_dir = build_dir / "dependencies"
|
|||
|
||||
# copy all files
|
||||
_print("Copying dependencies ...")
|
||||
shutil.copytree(site_pkg.as_posix(), deps_dir.as_posix())
|
||||
|
||||
total_files = count_folders(site_pkg)
|
||||
progress_bar = enlighten.Counter(
|
||||
total=total_files, desc="Processing Dependencies",
|
||||
units="%", color="green")
|
||||
|
||||
|
||||
def _progress(_base, _names):
|
||||
progress_bar.update()
|
||||
return []
|
||||
|
||||
|
||||
shutil.copytree(site_pkg.as_posix(),
|
||||
deps_dir.as_posix(),
|
||||
ignore=_progress)
|
||||
progress_bar.close()
|
||||
# iterate over frozen libs and create list to delete
|
||||
libs_dir = build_dir / "lib"
|
||||
|
||||
to_delete = []
|
||||
_print("Finding duplicates ...")
|
||||
# _print("Finding duplicates ...")
|
||||
deps_items = list(deps_dir.iterdir())
|
||||
item_count = len(list(libs_dir.iterdir()))
|
||||
find_progress_bar = enlighten.Counter(
|
||||
total=item_count, desc="Finding duplicates", units="%", color="yellow")
|
||||
|
||||
for d in libs_dir.iterdir():
|
||||
if (deps_dir / d.name) in deps_items:
|
||||
to_delete.append(d)
|
||||
_print(f"found {d}", 3)
|
||||
# _print(f"found {d}", 3)
|
||||
find_progress_bar.update()
|
||||
|
||||
find_progress_bar.close()
|
||||
# add openpype and igniter in libs too
|
||||
to_delete.append(libs_dir / "openpype")
|
||||
to_delete.append(libs_dir / "igniter")
|
||||
|
||||
# delete duplicates
|
||||
_print(f"Deleting {len(to_delete)} duplicates ...")
|
||||
# _print(f"Deleting {len(to_delete)} duplicates ...")
|
||||
delete_progress_bar = enlighten.Counter(
|
||||
total=len(to_delete), desc="Deleting duplicates", units="%", color="red")
|
||||
for d in to_delete:
|
||||
if d.is_dir():
|
||||
shutil.rmtree(d)
|
||||
else:
|
||||
d.unlink()
|
||||
delete_progress_bar.update()
|
||||
|
||||
delete_progress_bar.close()
|
||||
|
||||
end_time = time.time_ns()
|
||||
total_time = (end_time - start_time) / 1000000000
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Helper script to build OpenPype.
|
||||
Helper script to build OpenPype Installer.
|
||||
|
||||
.DESCRIPTION
|
||||
This script will detect Python installation, and build OpenPype to `build`
|
||||
directory using existing virtual environment created by Poetry (or
|
||||
by running `/tools/create_venv.ps1`). It will then shuffle dependencies in
|
||||
build folder to optimize for different Python versions (2/3) in Python host.
|
||||
This script will use already built OpenPype (in `build` directory) and
|
||||
create Windows installer from it using Inno Setup (https://jrsoftware.org/)
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
PS> .\build.ps1
|
||||
PS> .\build_win_installer.ps1
|
||||
|
||||
#>
|
||||
|
||||
|
|
@ -76,11 +74,19 @@ function Install-Poetry() {
|
|||
|
||||
$art = @"
|
||||
|
||||
▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀
|
||||
▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░
|
||||
▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄
|
||||
.---= [ by Pype Club ] =---.
|
||||
https://openpype.io
|
||||
. . .. . ..
|
||||
_oOOP3OPP3Op_. .
|
||||
.PPpo~. .. ~2p. .. .... . .
|
||||
.Ppo . .pPO3Op.. . O:. . . .
|
||||
.3Pp . oP3'. 'P33. . 4 .. . . . .. . . .
|
||||
.~OP 3PO. .Op3 : . .. _____ _____ _____
|
||||
.P3O . oP3oP3O3P' . . . . / /./ /./ /
|
||||
O3:. O3p~ . .:. . ./____/./____/ /____/
|
||||
'P . 3p3. oP3~. ..P:. . . .. . . .. . . .
|
||||
. ': . Po' .Opo'. .3O. . o[ by Pype Club ]]]==- - - . .
|
||||
. '_ .. . . _OP3.. . .https://openpype.io.. .
|
||||
~P3.OPPPO3OP~ . .. .
|
||||
. ' '. . .. . . . .. .
|
||||
|
||||
"@
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,11 @@
|
|||
|
||||
PS> .\create_env.ps1
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
Print verbose information from Poetry:
|
||||
PS> .\create_env.ps1 --verbose
|
||||
|
||||
#>
|
||||
|
||||
$arguments=$ARGS
|
||||
|
|
@ -98,17 +103,17 @@ $art = @"
|
|||
|
||||
. . .. . ..
|
||||
_oOOP3OPP3Op_. .
|
||||
.PPpo~· ·· ~2p. ·· ···· · ·
|
||||
·Ppo · .pPO3Op.· · O:· · · ·
|
||||
.3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · ·
|
||||
·~OP 3PO· .Op3 : · ·· _____ _____ _____
|
||||
·P3O · oP3oP3O3P' · · · · / /·/ /·/ /
|
||||
O3:· O3p~ · ·:· · ·/____/·/____/ /____/
|
||||
'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · ·
|
||||
· ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · ·
|
||||
· '_ .. · . _OP3·· · ·https://openpype.io·· ·
|
||||
~P3·OPPPO3OP~ · ·· ·
|
||||
· ' '· · ·· · · · ·· ·
|
||||
.PPpo~. .. ~2p. .. .... . .
|
||||
.Ppo . .pPO3Op.. . O:. . . .
|
||||
.3Pp . oP3'. 'P33. . 4 .. . . . .. . . .
|
||||
.~OP 3PO. .Op3 : . .. _____ _____ _____
|
||||
.P3O . oP3oP3O3P' . . . . / /./ /./ /
|
||||
O3:. O3p~ . .:. . ./____/./____/ /____/
|
||||
'P . 3p3. oP3~. ..P:. . . .. . . .. . . .
|
||||
. ': . Po' .Opo'. .3O. . o[ by Pype Club ]]]==- - - . .
|
||||
. '_ .. . . _OP3.. . .https://openpype.io.. .
|
||||
~P3.OPPPO3OP~ . .. .
|
||||
. ' '. . .. . . . .. .
|
||||
|
||||
|
||||
"@
|
||||
|
|
|
|||
|
|
@ -4,14 +4,19 @@
|
|||
|
||||
.DESCRIPTION
|
||||
This script will detect Python installation and run OpenPype to create
|
||||
zip. It needs mongodb running. I will create zip from current source code
|
||||
version and copy it top `%LOCALAPPDATA%/pypeclub/pype` if `--path` or `-p`
|
||||
zip. It will create zip from current source code
|
||||
version and copy it top `%LOCALAPPDATA%/pypeclub/openpype` if `--path` or `-p`
|
||||
argument is not used.
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
PS> .\create_zip.ps1
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
To put generated zip to C:\OpenPype directory:
|
||||
PS> .\create_zip.ps1 --path C:\OpenPype
|
||||
|
||||
#>
|
||||
|
||||
function Exit-WithCode($exitcode) {
|
||||
|
|
@ -52,17 +57,17 @@ $art = @"
|
|||
|
||||
. . .. . ..
|
||||
_oOOP3OPP3Op_. .
|
||||
.PPpo~· ·· ~2p. ·· ···· · ·
|
||||
·Ppo · .pPO3Op.· · O:· · · ·
|
||||
.3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · ·
|
||||
·~OP 3PO· .Op3 : · ·· _____ _____ _____
|
||||
·P3O · oP3oP3O3P' · · · · / /·/ /·/ /
|
||||
O3:· O3p~ · ·:· · ·/____/·/____/ /____/
|
||||
'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · ·
|
||||
· ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · ·
|
||||
· '_ .. · . _OP3·· · ·https://openpype.io·· ·
|
||||
~P3·OPPPO3OP~ · ·· ·
|
||||
· ' '· · ·· · · · ·· ·
|
||||
.PPpo~. .. ~2p. .. .... . .
|
||||
.Ppo . .pPO3Op.. . O:. . . .
|
||||
.3Pp . oP3'. 'P33. . 4 .. . . . .. . . .
|
||||
.~OP 3PO. .Op3 : . .. _____ _____ _____
|
||||
.P3O . oP3oP3O3P' . . . . / /./ /./ /
|
||||
O3:. O3p~ . .:. . ./____/./____/ /____/
|
||||
'P . 3p3. oP3~. ..P:. . . .. . . .. . . .
|
||||
. ': . Po' .Opo'. .3O. . o[ by Pype Club ]]]==- - - . .
|
||||
. '_ .. . . _OP3.. . .https://openpype.io.. .
|
||||
~P3.OPPPO3OP~ . .. .
|
||||
. ' '. . .. . . . .. .
|
||||
|
||||
"@
|
||||
|
||||
|
|
|
|||
|
|
@ -32,17 +32,17 @@ $art = @"
|
|||
|
||||
. . .. . ..
|
||||
_oOOP3OPP3Op_. .
|
||||
.PPpo~· ·· ~2p. ·· ···· · ·
|
||||
·Ppo · .pPO3Op.· · O:· · · ·
|
||||
.3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · ·
|
||||
·~OP 3PO· .Op3 : · ·· _____ _____ _____
|
||||
·P3O · oP3oP3O3P' · · · · / /·/ /·/ /
|
||||
O3:· O3p~ · ·:· · ·/____/·/____/ /____/
|
||||
'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · ·
|
||||
· ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · ·
|
||||
· '_ .. · . _OP3·· · ·https://openpype.io·· ·
|
||||
~P3·OPPPO3OP~ · ·· ·
|
||||
· ' '· · ·· · · · ·· ·
|
||||
.PPpo~. .. ~2p. .. .... . .
|
||||
.Ppo . .pPO3Op.. . O:. . . .
|
||||
.3Pp . oP3'. 'P33. . 4 .. . . . .. . . .
|
||||
.~OP 3PO. .Op3 : . .. _____ _____ _____
|
||||
.P3O . oP3oP3O3P' . . . . / /./ /./ /
|
||||
O3:. O3p~ . .:. . ./____/./____/ /____/
|
||||
'P . 3p3. oP3~. ..P:. . . .. . . .. . . .
|
||||
. ': . Po' .Opo'. .3O. . o[ by Pype Club ]]]==- - - . .
|
||||
. '_ .. . . _OP3.. . .https://openpype.io.. .
|
||||
~P3.OPPPO3OP~ . .. .
|
||||
. ' '. . .. . . . .. .
|
||||
|
||||
"@
|
||||
|
||||
|
|
|
|||
|
|
@ -1,23 +1,38 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Helper script to run mongodb.
|
||||
Helper script to run Docusaurus for easy editing of OpenPype documentation.
|
||||
|
||||
.DESCRIPTION
|
||||
This script will detect mongodb, add it to the PATH and launch it on specified port and db location.
|
||||
This script is using `yarn` package manager to run Docusaurus. If you don't
|
||||
have `yarn`, install Node.js (https://nodejs.org/) and then run:
|
||||
|
||||
npm install -g yarn
|
||||
|
||||
It take some time to run this script. If all is successful you should see
|
||||
new browser window with OpenPype documentation. All changes is markdown files
|
||||
under .\website should be immediately seen in browser.
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
PS> .\run_mongo.ps1
|
||||
PS> .\run_documentation.ps1
|
||||
|
||||
#>
|
||||
|
||||
$art = @"
|
||||
|
||||
▒█▀▀▀█ █▀▀█ █▀▀ █▀▀▄ ▒█▀▀█ █░░█ █▀▀█ █▀▀ ▀█▀ ▀█▀ ▀█▀
|
||||
▒█░░▒█ █░░█ █▀▀ █░░█ ▒█▄▄█ █▄▄█ █░░█ █▀▀ ▒█░ ▒█░ ▒█░
|
||||
▒█▄▄▄█ █▀▀▀ ▀▀▀ ▀░░▀ ▒█░░░ ▄▄▄█ █▀▀▀ ▀▀▀ ▄█▄ ▄█▄ ▄█▄
|
||||
.---= [ by Pype Club ] =---.
|
||||
https://openpype.io
|
||||
. . .. . ..
|
||||
_oOOP3OPP3Op_. .
|
||||
.PPpo~. .. ~2p. .. .... . .
|
||||
.Ppo . .pPO3Op.. . O:. . . .
|
||||
.3Pp . oP3'. 'P33. . 4 .. . . . .. . . .
|
||||
.~OP 3PO. .Op3 : . .. _____ _____ _____
|
||||
.P3O . oP3oP3O3P' . . . . / /./ /./ /
|
||||
O3:. O3p~ . .:. . ./____/./____/ /____/
|
||||
'P . 3p3. oP3~. ..P:. . . .. . . .. . . .
|
||||
. ': . Po' .Opo'. .3O. . o[ by Pype Club ]]]==- - - . .
|
||||
. '_ .. . . _OP3.. . .https://openpype.io.. .
|
||||
~P3.OPPPO3OP~ . .. .
|
||||
. ' '. . .. . . . .. .
|
||||
|
||||
"@
|
||||
|
||||
|
|
@ -26,7 +41,6 @@ Write-Host $art -ForegroundColor DarkGreen
|
|||
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
$openpype_root = (Get-Item $script_dir).parent.FullName
|
||||
|
||||
cd $openpype_root/website
|
||||
|
||||
yarn run start
|
||||
Set-Location $openpype_root/website
|
||||
|
||||
& yarn run start
|
||||
|
|
|
|||
|
|
@ -15,17 +15,17 @@ $art = @"
|
|||
|
||||
. . .. . ..
|
||||
_oOOP3OPP3Op_. .
|
||||
.PPpo~· ·· ~2p. ·· ···· · ·
|
||||
·Ppo · .pPO3Op.· · O:· · · ·
|
||||
.3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · ·
|
||||
·~OP 3PO· .Op3 : · ·· _____ _____ _____
|
||||
·P3O · oP3oP3O3P' · · · · / /·/ /·/ /
|
||||
O3:· O3p~ · ·:· · ·/____/·/____/ /____/
|
||||
'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · ·
|
||||
· ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · ·
|
||||
· '_ .. · . _OP3·· · ·https://openpype.io·· ·
|
||||
~P3·OPPPO3OP~ · ·· ·
|
||||
· ' '· · ·· · · · ·· ·
|
||||
.PPpo~. .. ~2p. .. .... . .
|
||||
.Ppo . .pPO3Op.. . O:. . . .
|
||||
.3Pp . oP3'. 'P33. . 4 .. . . . .. . . .
|
||||
.~OP 3PO. .Op3 : . .. _____ _____ _____
|
||||
.P3O . oP3oP3O3P' . . . . / /./ /./ /
|
||||
O3:. O3p~ . .:. . ./____/./____/ /____/
|
||||
'P . 3p3. oP3~. ..P:. . . .. . . .. . . .
|
||||
. ': . Po' .Opo'. .3O. . o[ by Pype Club ]]]==- - - . .
|
||||
. '_ .. . . _OP3.. . .https://openpype.io.. .
|
||||
~P3.OPPPO3OP~ . .. .
|
||||
. ' '. . .. . . . .. .
|
||||
|
||||
"@
|
||||
|
||||
|
|
|
|||
18
tools/run_project_manager.ps1
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<#
|
||||
.SYNOPSIS
|
||||
Helper script to run Project Manager.
|
||||
|
||||
.DESCRIPTION
|
||||
|
||||
|
||||
.EXAMPLE
|
||||
|
||||
PS> .\run_project_manager.ps1
|
||||
|
||||
#>
|
||||
$current_dir = Get-Location
|
||||
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
|
||||
$openpype_root = (Get-Item $script_dir).parent.FullName
|
||||
Set-Location -Path $openpype_root
|
||||
& poetry run python "$($openpype_root)\start.py" projectmanager
|
||||
Set-Location -Path $current_dir
|
||||
|
|
@ -34,17 +34,17 @@ $art = @"
|
|||
|
||||
. . .. . ..
|
||||
_oOOP3OPP3Op_. .
|
||||
.PPpo~· ·· ~2p. ·· ···· · ·
|
||||
·Ppo · .pPO3Op.· · O:· · · ·
|
||||
.3Pp · oP3'· 'P33· · 4 ·· · · · ·· · · ·
|
||||
·~OP 3PO· .Op3 : · ·· _____ _____ _____
|
||||
·P3O · oP3oP3O3P' · · · · / /·/ /·/ /
|
||||
O3:· O3p~ · ·:· · ·/____/·/____/ /____/
|
||||
'P · 3p3· oP3~· ·.P:· · · ·· · · ·· · · ·
|
||||
· ': · Po' ·Opo'· .3O· . o[ by Pype Club ]]]==- - - · ·
|
||||
· '_ .. · . _OP3·· · ·https://openpype.io·· ·
|
||||
~P3·OPPPO3OP~ · ·· ·
|
||||
· ' '· · ·· · · · ·· ·
|
||||
.PPpo~. .. ~2p. .. .... . .
|
||||
.Ppo . .pPO3Op.. . O:. . . .
|
||||
.3Pp . oP3'. 'P33. . 4 .. . . . .. . . .
|
||||
.~OP 3PO. .Op3 : . .. _____ _____ _____
|
||||
.P3O . oP3oP3O3P' . . . . / /./ /./ /
|
||||
O3:. O3p~ . .:. . ./____/./____/ /____/
|
||||
'P . 3p3. oP3~. ..P:. . . .. . . .. . . .
|
||||
. ': . Po' .Opo'. .3O. . o[ by Pype Club ]]]==- - - . .
|
||||
. '_ .. . . _OP3.. . .https://openpype.io.. .
|
||||
~P3.OPPPO3OP~ . .. .
|
||||
. ' '. . .. . . . .. .
|
||||
|
||||
"@
|
||||
|
||||
|
|
|
|||
82
website/docs/artist_install.md
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
id: artist_install
|
||||
title: Installation
|
||||
sidebar_label: Installation
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
OpenPype comes in packages for Windows (10 or Server), Mac OS X (Mojave or higher), and Linux distribution (Centos, Ubuntu), and you can install them on your machine the same way as you are used to.
|
||||
|
||||
:::important
|
||||
To install OpenPype you will need administrator permissions.
|
||||
:::
|
||||
|
||||
:::note pick your platform
|
||||
<Tabs
|
||||
defaultValue='win'
|
||||
values={[
|
||||
{label: 'Windows', value: 'win'},
|
||||
{label: 'Linux', value: 'linux'},
|
||||
{label: 'Mac OS X', value: 'mac'},
|
||||
]}>
|
||||
|
||||
<TabItem value='win'>
|
||||
|
||||
For installation on Windows, download and run the executable file `OpenPype-3.0.0.exe`.
|
||||
During the installation process, you can change the destination location path of the application,
|
||||
|
||||

|
||||
|
||||
and create an icon on the desktop.
|
||||
|
||||

|
||||
|
||||
</TabItem>
|
||||
|
||||
|
||||
<TabItem value='linux'>
|
||||
|
||||
For installation on your Linux distribution, download and unzip `OpenPype-3.0.0.zip`. A new folder `OpenPype-3.0.0` will be created.
|
||||
Inside this folder find and run `openpype_gui`,
|
||||
|
||||

|
||||
|
||||
</TabItem>
|
||||
|
||||
|
||||
<TabItem value='mac'>
|
||||
|
||||
For installation on Mac OS X, download and run dmg image file `OpenPype-3.0.0.dmg`.
|
||||
|
||||
Drag the OpenPype icon into the Application folder.
|
||||
|
||||

|
||||
|
||||
After the installation, you can find OpenPype among the other Applications.
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
:::
|
||||
|
||||
## Run OpenPype
|
||||
|
||||
To run OpenPype click on the icon or find executable file (e.g. `C:\Program Files (x86)\OpenPype\openpype_gui.exe`) in the application location.
|
||||
On the very first run of OpenPype the user will be asked for OpenPype Mongo URL.
|
||||
This piece of information will be provided by the administrator or project manager who set up the studio.
|
||||
|
||||

|
||||
|
||||
Once the Mongo URL address is entered, press `Start`, and OpenPype will be initiated.
|
||||
OpenPype will also remember the connection for the next launch, so it is a one-time process.
|
||||
|
||||
:::note
|
||||
If the launch was successful, the artist should see a turquoise OpenPype logo in their
|
||||
tray menu. Keep in mind that on Windows this icon might be hidden by default, in which case, the artist can simply drag the icon down to the tray.
|
||||
|
||||

|
||||
:::
|
||||
BIN
website/docs/assets/artist_systray.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
website/docs/assets/install_01.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
website/docs/assets/install_02.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
website/docs/assets/install_03.png
Normal file
|
After Width: | Height: | Size: 26 KiB |
BIN
website/docs/assets/install_04.png
Normal file
|
After Width: | Height: | Size: 31 KiB |
BIN
website/docs/assets/install_05.png
Normal file
|
After Width: | Height: | Size: 7.9 KiB |