Merge branch 'develop' into feature/extract_review_bg_color

This commit is contained in:
iLLiCiTiT 2021-05-25 10:32:27 +02:00
commit b9002ddf9f
60 changed files with 3688 additions and 406 deletions

View file

@ -23,18 +23,32 @@ def add_implementation_envs(env, _app):
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 ""
previous_user_scripts = []
for path in blender_user_scripts.split(os.pathsep):
if path and os.path.exists(path):
path = os.path.normpath(path)
if path != implementation_user_script_path:
previous_user_scripts.append(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
)
env["BLENDER_USER_SCRIPTS"] = implementation_user_script_path
# Define Qt binding if not defined
if not env.get("QT_PREFERRED_BINDING"):

View file

@ -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)

View 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()

View file

@ -1,25 +0,0 @@
import bpy
from avalon import api, blender
import openpype.hosts.blender.api.plugin
class CreateSetDress(openpype.hosts.blender.api.plugin.Creator):
"""A grouped package of loaded content"""
name = "setdressMain"
label = "Set Dress"
family = "setdress"
icon = "cubes"
defaults = ["Main", "Anim"]
def process(self):
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)
self.data['task'] = api.Session.get('AVALON_TASK')
blender.lib.imprint(collection, self.data)
return collection

View file

@ -25,9 +25,6 @@ class BlendLayoutLoader(plugin.AssetLoader):
icon = "code-fork"
color = "orange"
animation_creator_name = "CreateAnimation"
setdress_creator_name = "CreateSetDress"
def _remove(self, objects, obj_container):
for obj in list(objects):
if obj.type == 'ARMATURE':
@ -293,7 +290,6 @@ class UnrealLayoutLoader(plugin.AssetLoader):
color = "orange"
animation_creator_name = "CreateAnimation"
setdress_creator_name = "CreateSetDress"
def _remove_objects(self, objects):
for obj in list(objects):
@ -383,7 +379,7 @@ class UnrealLayoutLoader(plugin.AssetLoader):
def _process(
self, libpath, layout_container, container_name, representation,
actions, parent
actions, parent_collection
):
with open(libpath, "r") as fp:
data = json.load(fp)
@ -392,6 +388,11 @@ class UnrealLayoutLoader(plugin.AssetLoader):
layout_collection = bpy.data.collections.new(container_name)
scene.collection.children.link(layout_collection)
parent = parent_collection
if parent is None:
parent = scene.collection
all_loaders = api.discover(api.Loader)
avalon_container = bpy.data.collections.get(
@ -516,23 +517,9 @@ class UnrealLayoutLoader(plugin.AssetLoader):
container_metadata["libpath"] = libpath
container_metadata["lib_container"] = lib_container
# Create a setdress subset to contain all the animation for all
# the rigs in the layout
creator_plugin = get_creator_by_name(self.setdress_creator_name)
if not creator_plugin:
raise ValueError("Creator plugin \"{}\" was not found.".format(
self.setdress_creator_name
))
parent = api.create(
creator_plugin,
name="animation",
asset=api.Session["AVALON_ASSET"],
options={"useSelection": True},
data={"dependencies": str(context["representation"]["_id"])})
layout_collection = self._process(
libpath, layout_container, container_name,
str(context["representation"]["_id"]), None, parent)
str(context["representation"]["_id"]), None, None)
container_metadata["obj_container"] = layout_collection

View file

@ -107,6 +107,9 @@ class BlendRigLoader(plugin.AssetLoader):
if action is not None:
local_obj.animation_data.action = action
elif local_obj.animation_data.action is not None:
plugin.prepare_data(
local_obj.animation_data.action, collection_name)
# Set link the drivers to the local object
if local_obj.data.animation_data:

View file

@ -1,61 +0,0 @@
import os
import json
import openpype.api
import pyblish.api
import bpy
class ExtractSetDress(openpype.api.Extractor):
"""Extract setdress."""
label = "Extract SetDress"
hosts = ["blender"]
families = ["setdress"]
optional = True
order = pyblish.api.ExtractorOrder + 0.1
def process(self, instance):
stagingdir = self.staging_dir(instance)
json_data = []
for i in instance.context:
collection = i.data.get("name")
container = None
for obj in bpy.data.collections[collection].objects:
if obj.type == "ARMATURE":
container_name = obj.get("avalon").get("container_name")
container = bpy.data.collections[container_name]
if container:
json_dict = {
"subset": i.data.get("subset"),
"container": container.name,
}
json_dict["instance_name"] = container.get("avalon").get(
"instance_name"
)
json_data.append(json_dict)
if "representations" not in instance.data:
instance.data["representations"] = []
json_filename = f"{instance.name}.json"
json_path = os.path.join(stagingdir, json_filename)
with open(json_path, "w+") as file:
json.dump(json_data, fp=file, indent=2)
json_representation = {
"name": "json",
"ext": "json",
"files": json_filename,
"stagingDir": stagingdir,
}
instance.data["representations"].append(json_representation)
self.log.info(
"Extracted instance '{}' to: {}".format(instance.name,
json_representation)
)

View file

@ -1,4 +1,5 @@
import os
import json
import openpype.api
@ -121,6 +122,25 @@ class ExtractAnimationFBX(openpype.api.Extractor):
pair[1].user_clear()
bpy.data.actions.remove(pair[1])
json_filename = f"{instance.name}.json"
json_path = os.path.join(stagingdir, json_filename)
json_dict = {}
collection = instance.data.get("name")
container = None
for obj in bpy.data.collections[collection].objects:
if obj.type == "ARMATURE":
container_name = obj.get("avalon").get("container_name")
container = bpy.data.collections[container_name]
if container:
json_dict = {
"instance_name": container.get("avalon").get("instance_name")
}
with open(json_path, "w+") as file:
json.dump(json_dict, fp=file, indent=2)
if "representations" not in instance.data:
instance.data["representations"] = []
@ -130,7 +150,15 @@ class ExtractAnimationFBX(openpype.api.Extractor):
'files': fbx_filename,
"stagingDir": stagingdir,
}
json_representation = {
'name': 'json',
'ext': 'json',
'files': json_filename,
"stagingDir": stagingdir,
}
instance.data["representations"].append(fbx_representation)
instance.data["representations"].append(json_representation)
self.log.info("Extracted instance '{}' to: {}".format(
instance.name, fbx_representation))

View file

@ -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
)

View file

@ -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)

View file

@ -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():

View file

@ -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)

View file

@ -1,4 +1,5 @@
import os
import json
from avalon import api, pipeline
from avalon.unreal import lib
@ -61,10 +62,16 @@ class AnimationFBXLoader(api.Loader):
task = unreal.AssetImportTask()
task.options = unreal.FbxImportUI()
# If there are no options, the process cannot be automated
if options:
libpath = self.fname.replace("fbx", "json")
with open(libpath, "r") as fp:
data = json.load(fp)
instance_name = data.get("instance_name")
if instance_name:
automated = True
actor_name = 'PersistentLevel.' + options.get('instance_name')
actor_name = 'PersistentLevel.' + instance_name
actor = unreal.EditorLevelLibrary.get_actor_reference(actor_name)
skeleton = actor.skeletal_mesh_component.skeletal_mesh.skeleton
task.options.set_editor_property('skeleton', skeleton)
@ -81,16 +88,31 @@ class AnimationFBXLoader(api.Loader):
# set import options here
task.options.set_editor_property(
'automated_import_should_detect_type', True)
'automated_import_should_detect_type', False)
task.options.set_editor_property(
'original_import_type', unreal.FBXImportType.FBXIT_ANIMATION)
'original_import_type', unreal.FBXImportType.FBXIT_SKELETAL_MESH)
task.options.set_editor_property(
'mesh_type_to_import', unreal.FBXImportType.FBXIT_ANIMATION)
task.options.set_editor_property('import_mesh', False)
task.options.set_editor_property('import_animations', True)
task.options.set_editor_property('override_full_name', True)
task.options.skeletal_mesh_import_data.set_editor_property(
'import_content_type',
unreal.FBXImportContentType.FBXICT_SKINNING_WEIGHTS
task.options.anim_sequence_import_data.set_editor_property(
'animation_length',
unreal.FBXAnimationLengthImportType.FBXALIT_EXPORTED_TIME
)
task.options.anim_sequence_import_data.set_editor_property(
'import_meshes_in_bone_hierarchy', False)
task.options.anim_sequence_import_data.set_editor_property(
'use_default_sample_rate', True)
task.options.anim_sequence_import_data.set_editor_property(
'import_custom_attribute', True)
task.options.anim_sequence_import_data.set_editor_property(
'import_bone_tracks', True)
task.options.anim_sequence_import_data.set_editor_property(
'remove_redundant_keys', True)
task.options.anim_sequence_import_data.set_editor_property(
'convert_scene', True)
unreal.AssetToolsHelpers.get_asset_tools().import_asset_tasks([task])

View file

@ -1,127 +0,0 @@
import json
from avalon import api
import unreal
class AnimationCollectionLoader(api.Loader):
"""Load Unreal SkeletalMesh from FBX"""
families = ["setdress"]
representations = ["json"]
label = "Load Animation Collection"
icon = "cube"
color = "orange"
def load(self, context, name, namespace, options):
from avalon import api, pipeline
from avalon.unreal import lib
from avalon.unreal import pipeline as unreal_pipeline
import unreal
# Create directory for asset and avalon container
root = "/Game/Avalon/Assets"
asset = context.get('asset').get('name')
suffix = "_CON"
tools = unreal.AssetToolsHelpers().get_asset_tools()
asset_dir, container_name = tools.create_unique_asset_name(
"{}/{}".format(root, asset), suffix="")
container_name += suffix
unreal.EditorAssetLibrary.make_directory(asset_dir)
libpath = self.fname
with open(libpath, "r") as fp:
data = json.load(fp)
all_loaders = api.discover(api.Loader)
for element in data:
reference = element.get('_id')
loaders = api.loaders_from_representation(all_loaders, reference)
loader = None
for l in loaders:
if l.__name__ == "AnimationFBXLoader":
loader = l
break
if not loader:
continue
instance_name = element.get('instance_name')
api.load(
loader,
reference,
namespace=instance_name,
options=element
)
# Create Asset Container
lib.create_avalon_container(
container=container_name, path=asset_dir)
data = {
"schema": "openpype:container-2.0",
"id": pipeline.AVALON_CONTAINER_ID,
"asset": asset,
"namespace": asset_dir,
"container_name": container_name,
"loader": str(self.__class__.__name__),
"representation": context["representation"]["_id"],
"parent": context["representation"]["parent"],
"family": context["representation"]["context"]["family"]
}
unreal_pipeline.imprint(
"{}/{}".format(asset_dir, container_name), data)
asset_content = unreal.EditorAssetLibrary.list_assets(
asset_dir, recursive=True, include_folder=True
)
return asset_content
def update(self, container, representation):
from avalon import api, io
from avalon.unreal import pipeline
source_path = api.get_representation_path(representation)
with open(source_path, "r") as fp:
data = json.load(fp)
animation_containers = [
i for i in pipeline.ls() if
i.get('asset') == container.get('asset') and
i.get('family') == 'animation']
for element in data:
new_version = io.find_one({"_id": io.ObjectId(element.get('_id'))})
new_version_number = new_version.get('context').get('version')
anim_container = None
for i in animation_containers:
if i.get('container_name') == (element.get('subset') + "_CON"):
anim_container = i
break
if not anim_container:
continue
api.update(anim_container, new_version_number)
container_path = "{}/{}".format(container["namespace"],
container["objectName"])
# update metadata
pipeline.imprint(
container_path,
{
"representation": str(representation["_id"]),
"parent": str(representation["parent"])
})
def remove(self, container):
unreal.EditorAssetLibrary.delete_directory(container["namespace"])

View file

@ -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)

View file

@ -0,0 +1,17 @@
{
"create": {
"CreateImage": {
"defaults": [
"Main"
]
}
},
"publish": {
"ExtractImage": {
"formats": [
"png",
"jpg"
]
}
}
}

View file

@ -16,5 +16,21 @@
"active": true
}
},
"load": {
"LoadImage": {
"defaults": {
"stretch": true,
"timestretch": true,
"preload": true
}
},
"ImportImage": {
"defaults": {
"stretch": true,
"timestretch": true,
"preload": true
}
}
},
"filters": {}
}

View file

@ -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",

View file

@ -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):

View 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

View file

@ -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):

View file

@ -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

View file

@ -82,6 +82,10 @@
"type": "schema",
"name": "schema_project_aftereffects"
},
{
"type": "schema",
"name": "schema_project_photoshop"
},
{
"type": "schema",
"name": "schema_project_harmony"

View file

@ -10,7 +10,6 @@
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"is_file": true,
"children": [
{
"type": "dict",

View file

@ -603,7 +603,6 @@
"collapsible": true,
"key": "publish",
"label": "Publish plugins",
"is_file": true,
"children": [
{
"type": "dict",

View file

@ -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"
}
]
}
]
}
]
}

View file

@ -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",

View file

@ -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",

View file

@ -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"

View file

@ -242,14 +242,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",
@ -259,14 +254,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"
}
]
}

View file

@ -4,6 +4,11 @@
"type": "dict",
"is_file": true,
"children": [
{
"key": "color",
"label": "Color input",
"type": "color"
},
{
"type": "dict",
"key": "schema_template_exaples",

View file

@ -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"
}
]
},

View file

@ -1797,7 +1797,11 @@ class AssetItem(BaseItem):
item.setData(False, DUPLICATED_ROLE)
def _remove_task(self, item):
# This method is probably obsolete with changed logic and added
# `on_task_remove_state_change` method.
item_id = item.data(IDENTIFIER_ROLE)
if item_id not in self._task_name_by_item_id:
return
name = self._task_name_by_item_id.pop(item_id)
self._task_items_by_name[name].remove(item)
@ -1810,6 +1814,9 @@ class AssetItem(BaseItem):
_item.setData(False, DUPLICATED_ROLE)
def _rename_task(self, item):
if item.data(REMOVED_ROLE):
return
new_name = item.data(QtCore.Qt.EditRole, "name").lower()
item_id = item.data(IDENTIFIER_ROLE)
prev_name = self._task_name_by_item_id[item_id]
@ -1840,6 +1847,32 @@ class AssetItem(BaseItem):
def on_task_name_change(self, task_item):
self._rename_task(task_item)
def on_task_remove_state_change(self, task_item):
is_removed = task_item.data(REMOVED_ROLE)
item_id = task_item.data(IDENTIFIER_ROLE)
if is_removed:
name = self._task_name_by_item_id.pop(item_id)
self._task_items_by_name[name].remove(task_item)
else:
name = task_item.data(QtCore.Qt.EditRole, "name").lower()
self._task_name_by_item_id[item_id] = name
self._task_items_by_name[name].append(task_item)
# Remove from previous name mapping
if not self._task_items_by_name[name]:
self._task_items_by_name.pop(name)
elif len(self._task_items_by_name[name]) == 1:
if name in self._duplicated_task_names:
self._duplicated_task_names.remove(name)
task_item.setData(False, DUPLICATED_ROLE)
else:
self._duplicated_task_names.add(name)
for _item in self._task_items_by_name[name]:
_item.setData(True, DUPLICATED_ROLE)
def add_child(self, item, row=None):
if item in self._children:
return
@ -1975,7 +2008,10 @@ class TaskItem(BaseItem):
return True
if role == REMOVED_ROLE:
if value == self._removed:
return False
self._removed = value
self.parent().on_task_remove_state_change(self)
return True
if (

View file

@ -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)

View 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

View file

@ -0,0 +1,14 @@
from .color_picker_widget import (
ColorPickerWidget
)
from .color_view import (
draw_checkerboard_tile
)
__all__ = (
"ColorPickerWidget",
"draw_checkerboard_tile"
)

View 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)

View 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)

View 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()

File diff suppressed because it is too large Load diff

View 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()

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

@ -12,6 +12,14 @@
PS> .\build.ps1
.EXAMPLE
To build without automatical submodule update:
PS> .\build.ps1 --no-submodule-update
.LINK
https://openpype.io/docs
#>
$arguments=$ARGS
@ -82,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~ . .. .
. ' '. . .. . . . .. .
"@

View file

@ -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~ . .. .
. ' '. . .. . . . .. .
"@

View file

@ -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~ . .. .
. ' '. . .. . . . .. .
"@

View file

@ -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~ . .. .
. ' '. . .. . . . .. .
"@

View file

@ -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~ . .. .
. ' '. . .. . . . .. .
"@

View file

@ -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

View file

@ -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~ . .. .
. ' '. . .. . . . .. .
"@

View file

@ -1,18 +1,60 @@
<#
.SYNOPSIS
Helper script OpenPype Tray.
Helper script to run Project Manager.
.DESCRIPTION
.EXAMPLE
PS> .\run_tray.ps1
PS> .\run_project_manager.ps1
#>
$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~ . .. .
. ' '. . .. . . . .. .
"@
Write-Host $art -ForegroundColor DarkGreen
$current_dir = Get-Location
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$openpype_root = (Get-Item $script_dir).parent.FullName
$env:_INSIDE_OPENPYPE_TOOL = "1"
# make sure Poetry is in PATH
if (-not (Test-Path 'env:POETRY_HOME')) {
$env:POETRY_HOME = "$openpype_root\.poetry"
}
$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
Set-Location -Path $openpype_root
Write-Host ">>> " -NoNewline -ForegroundColor Green
Write-Host "Reading Poetry ... " -NoNewline
if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) {
Write-Host "NOT FOUND" -ForegroundColor Yellow
Write-Host "*** " -NoNewline -ForegroundColor Yellow
Write-Host "We need to install Poetry create virtual env first ..."
& "$openpype_root\tools\create_env.ps1"
} else {
Write-Host "OK" -ForegroundColor Green
}
& poetry run python "$($openpype_root)\start.py" projectmanager
Set-Location -Path $current_dir

103
tools/run_projectmanager.sh Executable file
View file

@ -0,0 +1,103 @@
#!/usr/bin/env bash
# Run OpenPype Settings GUI
art () {
cat <<-EOF
. . .. . ..
_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~ · ·· ·
· ' '· · ·· · · · ·· ·
EOF
}
# Colors for terminal
RST='\033[0m' # Text Reset
# Regular Colors
Black='\033[0;30m' # Black
Red='\033[0;31m' # Red
Green='\033[0;32m' # Green
Yellow='\033[0;33m' # Yellow
Blue='\033[0;34m' # Blue
Purple='\033[0;35m' # Purple
Cyan='\033[0;36m' # Cyan
White='\033[0;37m' # White
# Bold
BBlack='\033[1;30m' # Black
BRed='\033[1;31m' # Red
BGreen='\033[1;32m' # Green
BYellow='\033[1;33m' # Yellow
BBlue='\033[1;34m' # Blue
BPurple='\033[1;35m' # Purple
BCyan='\033[1;36m' # Cyan
BWhite='\033[1;37m' # White
# Bold High Intensity
BIBlack='\033[1;90m' # Black
BIRed='\033[1;91m' # Red
BIGreen='\033[1;92m' # Green
BIYellow='\033[1;93m' # Yellow
BIBlue='\033[1;94m' # Blue
BIPurple='\033[1;95m' # Purple
BICyan='\033[1;96m' # Cyan
BIWhite='\033[1;97m' # White
##############################################################################
# Return absolute path
# Globals:
# None
# Arguments:
# Path to resolve
# Returns:
# None
###############################################################################
realpath () {
echo $(cd $(dirname "$1"); pwd)/$(basename "$1")
}
# Main
main () {
# Directories
openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}")))
_inside_openpype_tool="1"
# make sure Poetry is in PATH
if [[ -z $POETRY_HOME ]]; then
export POETRY_HOME="$openpype_root/.poetry"
fi
export PATH="$POETRY_HOME/bin:$PATH"
pushd "$openpype_root" > /dev/null || return > /dev/null
echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c"
if [ -f "$POETRY_HOME/bin/poetry" ]; then
echo -e "${BIGreen}OK${RST}"
else
echo -e "${BIYellow}NOT FOUND${RST}"
echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..."
. "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; }
fi
echo -e "${BIGreen}>>>${RST} Generating zip from current sources ..."
poetry run python "$openpype_root/start.py" projectmanager
}
main

View file

@ -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~ . .. .
. ' '. . .. . . . .. .
"@

View 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,
![Windows installation](assets/install_01.png)
and create an icon on the desktop.
![Windows create icon](assets/install_02.png)
</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`,
![Linux launch](assets/install_03.png)
</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.
![Mac installation](assets/install_04.png)
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.
![Mongo example](assets/install_05.png)
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.
![Systray](assets/artist_systray.png)
:::

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

View file

@ -9,6 +9,7 @@ module.exports = {
"artist_concepts",
"artist_publish",
"artist_tools",
"artist_install"
],
},
{