[Automated] Merged develop into main
13
README.md
|
|
@ -29,7 +29,7 @@ The main things you will need to run and build OpenPype are:
|
|||
- PowerShell 5.0+ (Windows)
|
||||
- Bash (Linux)
|
||||
- [**Python 3.7.8**](#python) or higher
|
||||
- [**MongoDB**](#database)
|
||||
- [**MongoDB**](#database) (needed only for local development)
|
||||
|
||||
|
||||
It can be built and ran on all common platforms. We develop and test on the following:
|
||||
|
|
@ -126,6 +126,16 @@ pyenv local 3.7.9
|
|||
|
||||
### Linux
|
||||
|
||||
#### Docker
|
||||
Easiest way to build OpenPype on Linux is using [Docker](https://www.docker.com/). Just run:
|
||||
|
||||
```sh
|
||||
sudo ./tools/docker_build.sh
|
||||
```
|
||||
|
||||
If all is successful, you'll find built OpenPype in `./build/` folder.
|
||||
|
||||
#### Manual build
|
||||
You will need [Python 3.7](https://www.python.org/downloads/) and [git](https://git-scm.com/downloads). You'll also need [curl](https://curl.se) on systems that doesn't have one preinstalled.
|
||||
|
||||
To build Python related stuff, you need Python header files installed (`python3-dev` on Ubuntu for example).
|
||||
|
|
@ -133,7 +143,6 @@ To build Python related stuff, you need Python header files installed (`python3-
|
|||
You'll need also other tools to build
|
||||
some OpenPype dependencies like [CMake](https://cmake.org/). Python 3 should be part of all modern distributions. You can use your package manager to install **git** and **cmake**.
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Details for Ubuntu</summary>
|
||||
Install git, cmake and curl
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class CollectWorkfile(pyblish.api.ContextPlugin):
|
|||
"subset": subset,
|
||||
"label": scene_file,
|
||||
"family": family,
|
||||
"families": [family, "ftrack"],
|
||||
"families": [family],
|
||||
"representations": list()
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -26,6 +26,12 @@ INVENTORY_PATH = os.path.join(PLUGINS_DIR, "inventory")
|
|||
|
||||
|
||||
def install():
|
||||
from openpype.settings import get_project_settings
|
||||
|
||||
project_settings = get_project_settings(os.getenv("AVALON_PROJECT"))
|
||||
# process path mapping
|
||||
process_dirmap(project_settings)
|
||||
|
||||
pyblish.register_plugin_path(PUBLISH_PATH)
|
||||
avalon.register_plugin_path(avalon.Loader, LOAD_PATH)
|
||||
avalon.register_plugin_path(avalon.Creator, CREATE_PATH)
|
||||
|
|
@ -53,6 +59,40 @@ def install():
|
|||
avalon.data["familiesStateToggled"] = ["imagesequence"]
|
||||
|
||||
|
||||
def process_dirmap(project_settings):
|
||||
# type: (dict) -> None
|
||||
"""Go through all paths in Settings and set them using `dirmap`.
|
||||
|
||||
Args:
|
||||
project_settings (dict): Settings for current project.
|
||||
|
||||
"""
|
||||
if not project_settings["maya"].get("maya-dirmap"):
|
||||
return
|
||||
mapping = project_settings["maya"]["maya-dirmap"]["paths"] or {}
|
||||
mapping_enabled = project_settings["maya"]["maya-dirmap"]["enabled"]
|
||||
if not mapping or not mapping_enabled:
|
||||
return
|
||||
if mapping.get("source-path") and mapping_enabled is True:
|
||||
log.info("Processing directory mapping ...")
|
||||
cmds.dirmap(en=True)
|
||||
for k, sp in enumerate(mapping["source-path"]):
|
||||
try:
|
||||
print("{} -> {}".format(sp, mapping["destination-path"][k]))
|
||||
cmds.dirmap(m=(sp, mapping["destination-path"][k]))
|
||||
cmds.dirmap(m=(mapping["destination-path"][k], sp))
|
||||
except IndexError:
|
||||
# missing corresponding destination path
|
||||
log.error(("invalid dirmap mapping, missing corresponding"
|
||||
" destination directory."))
|
||||
break
|
||||
except RuntimeError:
|
||||
log.error("invalid path {} -> {}, mapping not registered".format(
|
||||
sp, mapping["destination-path"][k]
|
||||
))
|
||||
continue
|
||||
|
||||
|
||||
def uninstall():
|
||||
pyblish.deregister_plugin_path(PUBLISH_PATH)
|
||||
avalon.deregister_plugin_path(avalon.Loader, LOAD_PATH)
|
||||
|
|
|
|||
|
|
@ -51,7 +51,6 @@ class ExtractReviewDataLut(openpype.api.Extractor):
|
|||
|
||||
if "render.farm" in families:
|
||||
instance.data["families"].remove("review")
|
||||
instance.data["families"].remove("ftrack")
|
||||
|
||||
self.log.debug(
|
||||
"_ lutPath: {}".format(instance.data["lutPath"]))
|
||||
|
|
|
|||
|
|
@ -45,7 +45,6 @@ class ExtractReviewDataMov(openpype.api.Extractor):
|
|||
|
||||
if "render.farm" in families:
|
||||
instance.data["families"].remove("review")
|
||||
instance.data["families"].remove("ftrack")
|
||||
data = exporter.generate_mov(farm=True)
|
||||
|
||||
self.log.debug(
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ def get_renderer_variables(renderlayer, root):
|
|||
if extension is None:
|
||||
extension = "png"
|
||||
|
||||
if extension == "exr (multichannel)" or extension == "exr (deep)":
|
||||
if extension in ["exr (multichannel)", "exr (deep)"]:
|
||||
extension = "exr"
|
||||
|
||||
prefix_attr = "vraySettings.fileNamePrefix"
|
||||
|
|
@ -295,57 +295,70 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin):
|
|||
instance.data["toBeRenderedOn"] = "deadline"
|
||||
|
||||
filepath = None
|
||||
patches = (
|
||||
context.data["project_settings"].get(
|
||||
"deadline", {}).get(
|
||||
"publish", {}).get(
|
||||
"MayaSubmitDeadline", {}).get(
|
||||
"scene_patches", {})
|
||||
)
|
||||
|
||||
# Handle render/export from published scene or not ------------------
|
||||
if self.use_published:
|
||||
patched_files = []
|
||||
for i in context:
|
||||
if "workfile" in i.data["families"]:
|
||||
assert i.data["publish"] is True, (
|
||||
"Workfile (scene) must be published along")
|
||||
template_data = i.data.get("anatomyData")
|
||||
rep = i.data.get("representations")[0].get("name")
|
||||
template_data["representation"] = rep
|
||||
template_data["ext"] = rep
|
||||
template_data["comment"] = None
|
||||
anatomy_filled = anatomy.format(template_data)
|
||||
template_filled = anatomy_filled["publish"]["path"]
|
||||
filepath = os.path.normpath(template_filled)
|
||||
self.log.info("Using published scene for render {}".format(
|
||||
filepath))
|
||||
if "workfile" not in i.data["families"]:
|
||||
continue
|
||||
assert i.data["publish"] is True, (
|
||||
"Workfile (scene) must be published along")
|
||||
template_data = i.data.get("anatomyData")
|
||||
rep = i.data.get("representations")[0].get("name")
|
||||
template_data["representation"] = rep
|
||||
template_data["ext"] = rep
|
||||
template_data["comment"] = None
|
||||
anatomy_filled = anatomy.format(template_data)
|
||||
template_filled = anatomy_filled["publish"]["path"]
|
||||
filepath = os.path.normpath(template_filled)
|
||||
self.log.info("Using published scene for render {}".format(
|
||||
filepath))
|
||||
|
||||
if not os.path.exists(filepath):
|
||||
self.log.error("published scene does not exist!")
|
||||
raise
|
||||
# now we need to switch scene in expected files
|
||||
# because <scene> token will now point to published
|
||||
# scene file and that might differ from current one
|
||||
new_scene = os.path.splitext(
|
||||
os.path.basename(filepath))[0]
|
||||
orig_scene = os.path.splitext(
|
||||
os.path.basename(context.data["currentFile"]))[0]
|
||||
exp = instance.data.get("expectedFiles")
|
||||
if not os.path.exists(filepath):
|
||||
self.log.error("published scene does not exist!")
|
||||
raise
|
||||
# now we need to switch scene in expected files
|
||||
# because <scene> token will now point to published
|
||||
# scene file and that might differ from current one
|
||||
new_scene = os.path.splitext(
|
||||
os.path.basename(filepath))[0]
|
||||
orig_scene = os.path.splitext(
|
||||
os.path.basename(context.data["currentFile"]))[0]
|
||||
exp = instance.data.get("expectedFiles")
|
||||
|
||||
if isinstance(exp[0], dict):
|
||||
# we have aovs and we need to iterate over them
|
||||
new_exp = {}
|
||||
for aov, files in exp[0].items():
|
||||
replaced_files = []
|
||||
for f in files:
|
||||
replaced_files.append(
|
||||
f.replace(orig_scene, new_scene)
|
||||
)
|
||||
new_exp[aov] = replaced_files
|
||||
instance.data["expectedFiles"] = [new_exp]
|
||||
else:
|
||||
new_exp = []
|
||||
for f in exp:
|
||||
new_exp.append(
|
||||
if isinstance(exp[0], dict):
|
||||
# we have aovs and we need to iterate over them
|
||||
new_exp = {}
|
||||
for aov, files in exp[0].items():
|
||||
replaced_files = []
|
||||
for f in files:
|
||||
replaced_files.append(
|
||||
f.replace(orig_scene, new_scene)
|
||||
)
|
||||
instance.data["expectedFiles"] = [new_exp]
|
||||
self.log.info("Scene name was switched {} -> {}".format(
|
||||
orig_scene, new_scene
|
||||
))
|
||||
new_exp[aov] = replaced_files
|
||||
instance.data["expectedFiles"] = [new_exp]
|
||||
else:
|
||||
new_exp = []
|
||||
for f in exp:
|
||||
new_exp.append(
|
||||
f.replace(orig_scene, new_scene)
|
||||
)
|
||||
instance.data["expectedFiles"] = [new_exp]
|
||||
self.log.info("Scene name was switched {} -> {}".format(
|
||||
orig_scene, new_scene
|
||||
))
|
||||
# patch workfile is needed
|
||||
if filepath not in patched_files:
|
||||
patched_file = self._patch_workfile(filepath, patches)
|
||||
patched_files.append(patched_file)
|
||||
|
||||
all_instances = []
|
||||
for result in context.data["results"]:
|
||||
|
|
@ -868,10 +881,11 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin):
|
|||
payload["JobInfo"].update(job_info_ext)
|
||||
payload["PluginInfo"].update(plugin_info_ext)
|
||||
|
||||
envs = []
|
||||
for k, v in payload["JobInfo"].items():
|
||||
if k.startswith("EnvironmentKeyValue"):
|
||||
envs.append(v)
|
||||
envs = [
|
||||
v
|
||||
for k, v in payload["JobInfo"].items()
|
||||
if k.startswith("EnvironmentKeyValue")
|
||||
]
|
||||
|
||||
# add app name to environment
|
||||
envs.append(
|
||||
|
|
@ -892,11 +906,8 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin):
|
|||
envs.append(
|
||||
"OPENPYPE_ASS_EXPORT_STEP={}".format(1))
|
||||
|
||||
i = 0
|
||||
for e in envs:
|
||||
for i, e in enumerate(envs):
|
||||
payload["JobInfo"]["EnvironmentKeyValue{}".format(i)] = e
|
||||
i += 1
|
||||
|
||||
return payload
|
||||
|
||||
def _get_vray_render_payload(self, data):
|
||||
|
|
@ -1003,7 +1014,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin):
|
|||
|
||||
"""
|
||||
if 'verify' not in kwargs:
|
||||
kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True # noqa
|
||||
kwargs['verify'] = not os.getenv("OPENPYPE_DONT_VERIFY_SSL", True)
|
||||
# add 10sec timeout before bailing out
|
||||
kwargs['timeout'] = 10
|
||||
return requests.post(*args, **kwargs)
|
||||
|
|
@ -1022,7 +1033,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin):
|
|||
|
||||
"""
|
||||
if 'verify' not in kwargs:
|
||||
kwargs['verify'] = False if os.getenv("OPENPYPE_DONT_VERIFY_SSL", True) else True # noqa
|
||||
kwargs['verify'] = not os.getenv("OPENPYPE_DONT_VERIFY_SSL", True)
|
||||
# add 10sec timeout before bailing out
|
||||
kwargs['timeout'] = 10
|
||||
return requests.get(*args, **kwargs)
|
||||
|
|
@ -1069,3 +1080,43 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin):
|
|||
result = filename_zero.replace("\\", "/")
|
||||
|
||||
return result
|
||||
|
||||
def _patch_workfile(self, file, patches):
|
||||
# type: (str, dict) -> [str, None]
|
||||
"""Patch Maya scene.
|
||||
|
||||
This will take list of patches (lines to add) and apply them to
|
||||
*published* Maya scene file (that is used later for rendering).
|
||||
|
||||
Patches are dict with following structure::
|
||||
{
|
||||
"name": "Name of patch",
|
||||
"regex": "regex of line before patch",
|
||||
"line": "line to insert"
|
||||
}
|
||||
|
||||
Args:
|
||||
file (str): File to patch.
|
||||
patches (dict): Dictionary defining patches.
|
||||
|
||||
Returns:
|
||||
str: Patched file path or None
|
||||
|
||||
"""
|
||||
if os.path.splitext(file)[1].lower() != ".ma" or not patches:
|
||||
return None
|
||||
|
||||
compiled_regex = [re.compile(p["regex"]) for p in patches]
|
||||
with open(file, "r+") as pf:
|
||||
scene_data = pf.readlines()
|
||||
for ln, line in enumerate(scene_data):
|
||||
for i, r in enumerate(compiled_regex):
|
||||
if re.match(r, line):
|
||||
scene_data.insert(ln + 1, patches[i]["line"])
|
||||
pf.seek(0)
|
||||
pf.writelines(scene_data)
|
||||
pf.truncate()
|
||||
self.log.info(
|
||||
"Applied {} patch to scene.".format(
|
||||
patches[i]["name"]))
|
||||
return file
|
||||
|
|
|
|||
|
|
@ -181,6 +181,10 @@ class ValidateExpectedFiles(pyblish.api.InstancePlugin):
|
|||
"""Returns set of file names from metadata.json"""
|
||||
expected_files = set()
|
||||
|
||||
for file_name in repre["files"]:
|
||||
files = repre["files"]
|
||||
if not isinstance(files, list):
|
||||
files = [files]
|
||||
|
||||
for file_name in files:
|
||||
expected_files.add(file_name)
|
||||
return expected_files
|
||||
|
|
|
|||
|
|
@ -63,8 +63,9 @@ class CollectFtrackFamily(pyblish.api.InstancePlugin):
|
|||
self.log.debug("Adding ftrack family for '{}'".
|
||||
format(instance.data.get("family")))
|
||||
|
||||
if families and "ftrack" not in families:
|
||||
instance.data["families"].append("ftrack")
|
||||
if families:
|
||||
if "ftrack" not in families:
|
||||
instance.data["families"].append("ftrack")
|
||||
else:
|
||||
instance.data["families"] = ["ftrack"]
|
||||
else:
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ class ValidateEditorialAssetName(pyblish.api.ContextPlugin):
|
|||
def process(self, context):
|
||||
|
||||
asset_and_parents = self.get_parents(context)
|
||||
self.log.debug("__ asset_and_parents: {}".format(asset_and_parents))
|
||||
|
||||
if not io.Session:
|
||||
io.install()
|
||||
|
|
@ -25,7 +26,8 @@ class ValidateEditorialAssetName(pyblish.api.ContextPlugin):
|
|||
self.log.debug("__ db_assets: {}".format(db_assets))
|
||||
|
||||
asset_db_docs = {
|
||||
str(e["name"]): e["data"]["parents"] for e in db_assets}
|
||||
str(e["name"]): e["data"]["parents"]
|
||||
for e in db_assets}
|
||||
|
||||
self.log.debug("__ project_entities: {}".format(
|
||||
pformat(asset_db_docs)))
|
||||
|
|
@ -107,6 +109,7 @@ class ValidateEditorialAssetName(pyblish.api.ContextPlugin):
|
|||
parents = instance.data["parents"]
|
||||
|
||||
return_dict.update({
|
||||
asset: [p["entity_name"] for p in parents]
|
||||
asset: [p["entity_name"] for p in parents
|
||||
if p["entity_type"].lower() != "project"]
|
||||
})
|
||||
return return_dict
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@
|
|||
"group": "none",
|
||||
"limit": [],
|
||||
"jobInfo": {},
|
||||
"pluginInfo": {}
|
||||
"pluginInfo": {},
|
||||
"scene_patches": []
|
||||
},
|
||||
"NukeSubmitDeadline": {
|
||||
"enabled": true,
|
||||
|
|
|
|||
|
|
@ -304,7 +304,8 @@
|
|||
"aftereffects"
|
||||
],
|
||||
"families": [
|
||||
"render"
|
||||
"render",
|
||||
"workfile"
|
||||
],
|
||||
"tasks": [],
|
||||
"add_ftrack_family": true,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,19 @@
|
|||
"workfile": "ma",
|
||||
"yetiRig": "ma"
|
||||
},
|
||||
"maya-dirmap": {
|
||||
"enabled": true,
|
||||
"paths": {
|
||||
"source-path": [
|
||||
"foo1",
|
||||
"foo2"
|
||||
],
|
||||
"destination-path": [
|
||||
"bar1",
|
||||
"bar2"
|
||||
]
|
||||
}
|
||||
},
|
||||
"scriptsmenu": {
|
||||
"name": "OpenPype Tools",
|
||||
"definition": [
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"project_setup": {
|
||||
"dev_mode": true,
|
||||
"install_unreal_python_engine": false
|
||||
"dev_mode": true
|
||||
}
|
||||
}
|
||||
|
|
@ -174,6 +174,14 @@ class BaseItemEntity(BaseEntity):
|
|||
roles = [roles]
|
||||
self.roles = roles
|
||||
|
||||
@abstractmethod
|
||||
def collect_static_entities_by_path(self):
|
||||
"""Collect all paths of all static path entities.
|
||||
|
||||
Static path is entity which is not dynamic or under dynamic entity.
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
def require_restart_on_change(self):
|
||||
return self._require_restart_on_change
|
||||
|
|
|
|||
|
|
@ -141,6 +141,7 @@ class DictConditionalEntity(ItemEntity):
|
|||
self.enum_key = self.schema_data.get("enum_key")
|
||||
self.enum_label = self.schema_data.get("enum_label")
|
||||
self.enum_children = self.schema_data.get("enum_children")
|
||||
self.enum_default = self.schema_data.get("enum_default")
|
||||
|
||||
self.enum_entity = None
|
||||
|
||||
|
|
@ -277,15 +278,22 @@ class DictConditionalEntity(ItemEntity):
|
|||
if isinstance(item, dict) and "key" in item:
|
||||
valid_enum_items.append(item)
|
||||
|
||||
enum_keys = []
|
||||
enum_items = []
|
||||
for item in valid_enum_items:
|
||||
item_key = item["key"]
|
||||
enum_keys.append(item_key)
|
||||
item_label = item.get("label") or item_key
|
||||
enum_items.append({item_key: item_label})
|
||||
|
||||
if not enum_items:
|
||||
return
|
||||
|
||||
if self.enum_default in enum_keys:
|
||||
default_key = self.enum_default
|
||||
else:
|
||||
default_key = enum_keys[0]
|
||||
|
||||
# Create Enum child first
|
||||
enum_key = self.enum_key or "invalid"
|
||||
enum_schema = {
|
||||
|
|
@ -293,7 +301,8 @@ class DictConditionalEntity(ItemEntity):
|
|||
"multiselection": False,
|
||||
"enum_items": enum_items,
|
||||
"key": enum_key,
|
||||
"label": self.enum_label
|
||||
"label": self.enum_label,
|
||||
"default": default_key
|
||||
}
|
||||
|
||||
enum_entity = self.create_schema_object(enum_schema, self)
|
||||
|
|
@ -318,6 +327,11 @@ class DictConditionalEntity(ItemEntity):
|
|||
|
||||
self.non_gui_children[item_key][child_obj.key] = child_obj
|
||||
|
||||
def collect_static_entities_by_path(self):
|
||||
if self.is_dynamic_item or self.is_in_dynamic_item:
|
||||
return {}
|
||||
return {self.path: self}
|
||||
|
||||
def get_child_path(self, child_obj):
|
||||
"""Get hierarchical path of child entity.
|
||||
|
||||
|
|
|
|||
|
|
@ -203,6 +203,18 @@ class DictImmutableKeysEntity(ItemEntity):
|
|||
)
|
||||
self.show_borders = self.schema_data.get("show_borders", True)
|
||||
|
||||
def collect_static_entities_by_path(self):
|
||||
output = {}
|
||||
if self.is_dynamic_item or self.is_in_dynamic_item:
|
||||
return output
|
||||
|
||||
output[self.path] = self
|
||||
for children in self.non_gui_children.values():
|
||||
result = children.collect_static_entities_by_path()
|
||||
if result:
|
||||
output.update(result)
|
||||
return output
|
||||
|
||||
def get_child_path(self, child_obj):
|
||||
"""Get hierarchical path of child entity.
|
||||
|
||||
|
|
|
|||
|
|
@ -73,21 +73,41 @@ class EnumEntity(BaseEnumEntity):
|
|||
def _item_initalization(self):
|
||||
self.multiselection = self.schema_data.get("multiselection", False)
|
||||
self.enum_items = self.schema_data.get("enum_items")
|
||||
# Default is optional and non breaking attribute
|
||||
enum_default = self.schema_data.get("default")
|
||||
|
||||
valid_keys = set()
|
||||
all_keys = []
|
||||
for item in self.enum_items or []:
|
||||
valid_keys.add(tuple(item.keys())[0])
|
||||
key = tuple(item.keys())[0]
|
||||
all_keys.append(key)
|
||||
|
||||
self.valid_keys = valid_keys
|
||||
self.valid_keys = set(all_keys)
|
||||
|
||||
if self.multiselection:
|
||||
self.valid_value_types = (list, )
|
||||
self.value_on_not_set = []
|
||||
value_on_not_set = []
|
||||
if enum_default:
|
||||
if not isinstance(enum_default, list):
|
||||
enum_default = [enum_default]
|
||||
|
||||
for item in enum_default:
|
||||
if item in all_keys:
|
||||
value_on_not_set.append(item)
|
||||
|
||||
self.value_on_not_set = value_on_not_set
|
||||
|
||||
else:
|
||||
for key in valid_keys:
|
||||
if self.value_on_not_set is NOT_SET:
|
||||
self.value_on_not_set = key
|
||||
break
|
||||
if isinstance(enum_default, list) and enum_default:
|
||||
enum_default = enum_default[0]
|
||||
|
||||
if enum_default in self.valid_keys:
|
||||
self.value_on_not_set = enum_default
|
||||
|
||||
else:
|
||||
for key in all_keys:
|
||||
if self.value_on_not_set is NOT_SET:
|
||||
self.value_on_not_set = key
|
||||
break
|
||||
|
||||
self.valid_value_types = (STRING_TYPE, )
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,11 @@ class EndpointEntity(ItemEntity):
|
|||
def _settings_value(self):
|
||||
pass
|
||||
|
||||
def collect_static_entities_by_path(self):
|
||||
if self.is_dynamic_item or self.is_in_dynamic_item:
|
||||
return {}
|
||||
return {self.path: self}
|
||||
|
||||
def settings_value(self):
|
||||
if self._override_state is OverrideState.NOT_DEFINED:
|
||||
return NOT_SET
|
||||
|
|
|
|||
|
|
@ -106,6 +106,9 @@ class PathEntity(ItemEntity):
|
|||
self.valid_value_types = valid_value_types
|
||||
self.child_obj = self.create_schema_object(item_schema, self)
|
||||
|
||||
def collect_static_entities_by_path(self):
|
||||
return self.child_obj.collect_static_entities_by_path()
|
||||
|
||||
def get_child_path(self, _child_obj):
|
||||
return self.path
|
||||
|
||||
|
|
@ -192,6 +195,24 @@ class PathEntity(ItemEntity):
|
|||
class ListStrictEntity(ItemEntity):
|
||||
schema_types = ["list-strict"]
|
||||
|
||||
def __getitem__(self, idx):
|
||||
if not isinstance(idx, int):
|
||||
idx = int(idx)
|
||||
return self.children[idx]
|
||||
|
||||
def __setitem__(self, idx, value):
|
||||
if not isinstance(idx, int):
|
||||
idx = int(idx)
|
||||
self.children[idx].set(value)
|
||||
|
||||
def get(self, idx, default=None):
|
||||
if not isinstance(idx, int):
|
||||
idx = int(idx)
|
||||
|
||||
if idx < len(self.children):
|
||||
return self.children[idx]
|
||||
return default
|
||||
|
||||
def _item_initalization(self):
|
||||
self.valid_value_types = (list, )
|
||||
self.require_key = True
|
||||
|
|
@ -222,6 +243,18 @@ class ListStrictEntity(ItemEntity):
|
|||
|
||||
super(ListStrictEntity, self).schema_validations()
|
||||
|
||||
def collect_static_entities_by_path(self):
|
||||
output = {}
|
||||
if self.is_dynamic_item or self.is_in_dynamic_item:
|
||||
return output
|
||||
|
||||
output[self.path] = self
|
||||
for child_obj in self.children:
|
||||
result = child_obj.collect_static_entities_by_path()
|
||||
if result:
|
||||
output.update(result)
|
||||
return output
|
||||
|
||||
def get_child_path(self, child_obj):
|
||||
result_idx = None
|
||||
for idx, _child_obj in enumerate(self.children):
|
||||
|
|
|
|||
|
|
@ -45,6 +45,24 @@ class ListEntity(EndpointEntity):
|
|||
return True
|
||||
return False
|
||||
|
||||
def __getitem__(self, idx):
|
||||
if not isinstance(idx, int):
|
||||
idx = int(idx)
|
||||
return self.children[idx]
|
||||
|
||||
def __setitem__(self, idx, value):
|
||||
if not isinstance(idx, int):
|
||||
idx = int(idx)
|
||||
self.children[idx].set(value)
|
||||
|
||||
def get(self, idx, default=None):
|
||||
if not isinstance(idx, int):
|
||||
idx = int(idx)
|
||||
|
||||
if idx < len(self.children):
|
||||
return self.children[idx]
|
||||
return default
|
||||
|
||||
def index(self, item):
|
||||
if isinstance(item, BaseEntity):
|
||||
for idx, child_entity in enumerate(self.children):
|
||||
|
|
|
|||
|
|
@ -242,6 +242,14 @@ class RootEntity(BaseItemEntity):
|
|||
"""Whan any children has changed."""
|
||||
self.on_change()
|
||||
|
||||
def collect_static_entities_by_path(self):
|
||||
output = {}
|
||||
for child_obj in self.non_gui_children.values():
|
||||
result = child_obj.collect_static_entities_by_path()
|
||||
if result:
|
||||
output.update(result)
|
||||
return output
|
||||
|
||||
def get_child_path(self, child_entity):
|
||||
"""Return path of children entity"""
|
||||
for key, _child_entity in self.non_gui_children.items():
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@
|
|||
- all items in `enum_children` must have at least `key` key which represents value stored under `enum_key`
|
||||
- items can define `label` for UI purposes
|
||||
- most important part is that item can define `children` key where are definitions of it's children (`children` value works the same way as in `dict`)
|
||||
- to set default value for `enum_key` set it with `enum_default`
|
||||
- entity must have defined `"label"` if is not used as widget
|
||||
- is set as group if any parent is not group
|
||||
- if `"label"` is entetered there which will be shown in GUI
|
||||
|
|
@ -359,6 +360,9 @@ How output of the schema could look like on save:
|
|||
- values are defined under value of key `"enum_items"` as list
|
||||
- each item in list is simple dictionary where value is label and key is value which will be stored
|
||||
- should be possible to enter single dictionary if order of items doesn't matter
|
||||
- it is possible to set default selected value/s with `default` attribute
|
||||
- it is recommended to use this option only in single selection mode
|
||||
- at the end this option is used only when defying default settings value or in dynamic items
|
||||
|
||||
```
|
||||
{
|
||||
|
|
@ -371,7 +375,7 @@ How output of the schema could look like on save:
|
|||
{"ftrackreview": "Add to Ftrack"},
|
||||
{"delete": "Delete output"},
|
||||
{"slate-frame": "Add slate frame"},
|
||||
{"no-hnadles": "Skip handle frames"}
|
||||
{"no-handles": "Skip handle frames"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -151,7 +151,7 @@
|
|||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"key": "MayaSubmitDeadline",
|
||||
"label": "Submit maya job to deadline",
|
||||
"label": "Submit Maya job to Deadline",
|
||||
"checkbox_key": "enabled",
|
||||
"children": [
|
||||
{
|
||||
|
|
@ -213,6 +213,31 @@
|
|||
"type": "raw-json",
|
||||
"key": "pluginInfo",
|
||||
"label": "Additional PluginInfo data"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"key": "scene_patches",
|
||||
"label": "Scene patches",
|
||||
"required_keys": ["name", "regex", "line"],
|
||||
"object_type": {
|
||||
"type": "dict",
|
||||
"children": [
|
||||
{
|
||||
"key": "name",
|
||||
"label": "Patch name",
|
||||
"type": "text"
|
||||
}, {
|
||||
"key": "regex",
|
||||
"label": "Patch regex",
|
||||
"type": "text"
|
||||
}, {
|
||||
"key": "line",
|
||||
"label": "Patch line",
|
||||
"type": "text"
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
|||
|
|
@ -14,6 +14,39 @@
|
|||
"type": "text"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"collapsible": true,
|
||||
"checkbox_key": "enabled",
|
||||
"key": "maya-dirmap",
|
||||
"label": "Maya Directory Mapping",
|
||||
"is_group": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "boolean",
|
||||
"key": "enabled",
|
||||
"label": "Enabled"
|
||||
},
|
||||
{
|
||||
"type": "dict",
|
||||
"key": "paths",
|
||||
"children": [
|
||||
{
|
||||
"type": "list",
|
||||
"object_type": "text",
|
||||
"key": "source-path",
|
||||
"label": "Source Path"
|
||||
},
|
||||
{
|
||||
"type": "list",
|
||||
"object_type": "text",
|
||||
"key": "destination-path",
|
||||
"label": "Destination Path"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "schema",
|
||||
"name": "schema_maya_scriptsmenu"
|
||||
|
|
|
|||
|
|
@ -25,6 +25,38 @@ class BaseWidget(QtWidgets.QWidget):
|
|||
self.label_widget = None
|
||||
self.create_ui()
|
||||
|
||||
def scroll_to(self, widget):
|
||||
self.category_widget.scroll_to(widget)
|
||||
|
||||
def set_path(self, path):
|
||||
self.category_widget.set_path(path)
|
||||
|
||||
def set_focus(self, scroll_to=False):
|
||||
"""Set focus of a widget.
|
||||
|
||||
Args:
|
||||
scroll_to(bool): Also scroll to widget in category widget.
|
||||
"""
|
||||
if scroll_to:
|
||||
self.scroll_to(self)
|
||||
self.setFocus()
|
||||
|
||||
def make_sure_is_visible(self, path, scroll_to):
|
||||
"""Make a widget of entity visible by it's path.
|
||||
|
||||
Args:
|
||||
path(str): Path to entity.
|
||||
scroll_to(bool): Should be scrolled to entity.
|
||||
|
||||
Returns:
|
||||
bool: Entity with path was found.
|
||||
"""
|
||||
raise NotImplementedError(
|
||||
"{} not implemented `make_sure_is_visible`".format(
|
||||
self.__class__.__name__
|
||||
)
|
||||
)
|
||||
|
||||
def trigger_hierarchical_style_update(self):
|
||||
self.category_widget.hierarchical_style_update()
|
||||
|
||||
|
|
@ -277,11 +309,23 @@ class BaseWidget(QtWidgets.QWidget):
|
|||
if to_run:
|
||||
to_run()
|
||||
|
||||
def focused_in(self):
|
||||
if self.entity is not None:
|
||||
self.set_path(self.entity.path)
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if self.allow_actions and event.button() == QtCore.Qt.RightButton:
|
||||
return self.show_actions_menu()
|
||||
|
||||
return super(BaseWidget, self).mouseReleaseEvent(event)
|
||||
focused_in = False
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
focused_in = True
|
||||
self.focused_in()
|
||||
|
||||
result = super(BaseWidget, self).mouseReleaseEvent(event)
|
||||
if focused_in and not event.isAccepted():
|
||||
event.accept()
|
||||
return result
|
||||
|
||||
|
||||
class InputWidget(BaseWidget):
|
||||
|
|
@ -337,6 +381,14 @@ class InputWidget(BaseWidget):
|
|||
)
|
||||
)
|
||||
|
||||
def make_sure_is_visible(self, path, scroll_to):
|
||||
if path:
|
||||
entity_path = self.entity.path
|
||||
if entity_path == path:
|
||||
self.set_focus(scroll_to)
|
||||
return True
|
||||
return False
|
||||
|
||||
def update_style(self):
|
||||
has_unsaved_changes = self.entity.has_unsaved_changes
|
||||
if not has_unsaved_changes and self.entity.group_item:
|
||||
|
|
@ -422,11 +474,20 @@ class GUIWidget(BaseWidget):
|
|||
layout.addWidget(splitter_item)
|
||||
|
||||
def set_entity_value(self):
|
||||
return
|
||||
pass
|
||||
|
||||
def hierarchical_style_update(self):
|
||||
pass
|
||||
|
||||
def make_sure_is_visible(self, *args, **kwargs):
|
||||
return False
|
||||
|
||||
def focused_in(self):
|
||||
pass
|
||||
|
||||
def set_path(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
def get_invalid(self):
|
||||
return []
|
||||
|
||||
|
|
|
|||
492
openpype/tools/settings/settings/breadcrumbs_widget.py
Normal file
|
|
@ -0,0 +1,492 @@
|
|||
from Qt import QtWidgets, QtGui, QtCore
|
||||
|
||||
PREFIX_ROLE = QtCore.Qt.UserRole + 1
|
||||
LAST_SEGMENT_ROLE = QtCore.Qt.UserRole + 2
|
||||
|
||||
|
||||
class BreadcrumbItem(QtGui.QStandardItem):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._display_value = None
|
||||
self._edit_value = None
|
||||
super(BreadcrumbItem, self).__init__(*args, **kwargs)
|
||||
|
||||
def data(self, role=None):
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
return self._display_value
|
||||
|
||||
if role == QtCore.Qt.EditRole:
|
||||
return self._edit_value
|
||||
|
||||
if role is None:
|
||||
args = tuple()
|
||||
else:
|
||||
args = (role, )
|
||||
return super(BreadcrumbItem, self).data(*args)
|
||||
|
||||
def setData(self, value, role):
|
||||
if role == QtCore.Qt.DisplayRole:
|
||||
self._display_value = value
|
||||
return True
|
||||
|
||||
if role == QtCore.Qt.EditRole:
|
||||
self._edit_value = value
|
||||
return True
|
||||
|
||||
if role is None:
|
||||
args = (value, )
|
||||
else:
|
||||
args = (value, role)
|
||||
return super(BreadcrumbItem, self).setData(*args)
|
||||
|
||||
|
||||
class BreadcrumbsModel(QtGui.QStandardItemModel):
|
||||
def __init__(self):
|
||||
super(BreadcrumbsModel, self).__init__()
|
||||
self.current_path = ""
|
||||
|
||||
self.reset()
|
||||
|
||||
def reset(self):
|
||||
return
|
||||
|
||||
|
||||
class SettingsBreadcrumbs(BreadcrumbsModel):
|
||||
def __init__(self):
|
||||
self.entity = None
|
||||
|
||||
self.entities_by_path = {}
|
||||
self.dynamic_paths = set()
|
||||
|
||||
super(SettingsBreadcrumbs, self).__init__()
|
||||
|
||||
def set_entity(self, entity):
|
||||
self.entities_by_path = {}
|
||||
self.dynamic_paths = set()
|
||||
self.entity = entity
|
||||
self.reset()
|
||||
|
||||
def has_children(self, path):
|
||||
for key in self.entities_by_path.keys():
|
||||
if key.startswith(path):
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_valid_path(self, path):
|
||||
if not path:
|
||||
return True
|
||||
|
||||
path_items = path.split("/")
|
||||
try:
|
||||
entity = self.entity
|
||||
for item in path_items:
|
||||
entity = entity[item]
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class SystemSettingsBreadcrumbs(SettingsBreadcrumbs):
|
||||
def reset(self):
|
||||
root_item = self.invisibleRootItem()
|
||||
rows = root_item.rowCount()
|
||||
if rows > 0:
|
||||
root_item.removeRows(0, rows)
|
||||
|
||||
if self.entity is None:
|
||||
return
|
||||
|
||||
entities_by_path = self.entity.collect_static_entities_by_path()
|
||||
self.entities_by_path = entities_by_path
|
||||
items = []
|
||||
for path in entities_by_path.keys():
|
||||
if not path:
|
||||
continue
|
||||
path_items = path.split("/")
|
||||
value = path
|
||||
label = path_items.pop(-1)
|
||||
prefix = "/".join(path_items)
|
||||
if prefix:
|
||||
prefix += "/"
|
||||
|
||||
item = QtGui.QStandardItem(value)
|
||||
item.setData(label, LAST_SEGMENT_ROLE)
|
||||
item.setData(prefix, PREFIX_ROLE)
|
||||
|
||||
items.append(item)
|
||||
|
||||
root_item.appendRows(items)
|
||||
|
||||
|
||||
class ProjectSettingsBreadcrumbs(SettingsBreadcrumbs):
|
||||
def reset(self):
|
||||
root_item = self.invisibleRootItem()
|
||||
rows = root_item.rowCount()
|
||||
if rows > 0:
|
||||
root_item.removeRows(0, rows)
|
||||
|
||||
if self.entity is None:
|
||||
return
|
||||
|
||||
entities_by_path = self.entity.collect_static_entities_by_path()
|
||||
self.entities_by_path = entities_by_path
|
||||
items = []
|
||||
for path in entities_by_path.keys():
|
||||
if not path:
|
||||
continue
|
||||
path_items = path.split("/")
|
||||
value = path
|
||||
label = path_items.pop(-1)
|
||||
prefix = "/".join(path_items)
|
||||
if prefix:
|
||||
prefix += "/"
|
||||
|
||||
item = QtGui.QStandardItem(value)
|
||||
item.setData(label, LAST_SEGMENT_ROLE)
|
||||
item.setData(prefix, PREFIX_ROLE)
|
||||
|
||||
items.append(item)
|
||||
|
||||
root_item.appendRows(items)
|
||||
|
||||
|
||||
class BreadcrumbsProxy(QtCore.QSortFilterProxyModel):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(BreadcrumbsProxy, self).__init__(*args, **kwargs)
|
||||
|
||||
self._current_path = ""
|
||||
|
||||
def set_path_prefix(self, prefix):
|
||||
path = prefix
|
||||
if not prefix.endswith("/"):
|
||||
path_items = path.split("/")
|
||||
if len(path_items) == 1:
|
||||
path = ""
|
||||
else:
|
||||
path_items.pop(-1)
|
||||
path = "/".join(path_items) + "/"
|
||||
|
||||
if path == self._current_path:
|
||||
return
|
||||
|
||||
self._current_path = prefix
|
||||
|
||||
self.invalidateFilter()
|
||||
|
||||
def filterAcceptsRow(self, row, parent):
|
||||
index = self.sourceModel().index(row, 0, parent)
|
||||
prefix_path = index.data(PREFIX_ROLE)
|
||||
return prefix_path == self._current_path
|
||||
|
||||
|
||||
class BreadcrumbsHintMenu(QtWidgets.QMenu):
|
||||
def __init__(self, model, path_prefix, parent):
|
||||
super(BreadcrumbsHintMenu, self).__init__(parent)
|
||||
|
||||
self._path_prefix = path_prefix
|
||||
self._model = model
|
||||
|
||||
def showEvent(self, event):
|
||||
self.clear()
|
||||
|
||||
self._model.set_path_prefix(self._path_prefix)
|
||||
|
||||
row_count = self._model.rowCount()
|
||||
if row_count == 0:
|
||||
action = self.addAction("* Nothing")
|
||||
action.setData(".")
|
||||
else:
|
||||
for row in range(self._model.rowCount()):
|
||||
index = self._model.index(row, 0)
|
||||
label = index.data(LAST_SEGMENT_ROLE)
|
||||
value = index.data(QtCore.Qt.EditRole)
|
||||
action = self.addAction(label)
|
||||
action.setData(value)
|
||||
|
||||
super(BreadcrumbsHintMenu, self).showEvent(event)
|
||||
|
||||
|
||||
class ClickableWidget(QtWidgets.QWidget):
|
||||
clicked = QtCore.Signal()
|
||||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if event.button() == QtCore.Qt.LeftButton:
|
||||
self.clicked.emit()
|
||||
super(ClickableWidget, self).mouseReleaseEvent(event)
|
||||
|
||||
|
||||
class BreadcrumbsPathInput(QtWidgets.QLineEdit):
|
||||
cancelled = QtCore.Signal()
|
||||
confirmed = QtCore.Signal()
|
||||
|
||||
def __init__(self, model, proxy_model, parent):
|
||||
super(BreadcrumbsPathInput, self).__init__(parent)
|
||||
|
||||
self.setObjectName("BreadcrumbsPathInput")
|
||||
|
||||
self.setFrame(False)
|
||||
|
||||
completer = QtWidgets.QCompleter(self)
|
||||
completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
|
||||
completer.setModel(proxy_model)
|
||||
|
||||
popup = completer.popup()
|
||||
popup.setUniformItemSizes(True)
|
||||
popup.setLayoutMode(QtWidgets.QListView.Batched)
|
||||
|
||||
self.setCompleter(completer)
|
||||
|
||||
completer.activated.connect(self._on_completer_activated)
|
||||
self.textEdited.connect(self._on_text_change)
|
||||
|
||||
self._completer = completer
|
||||
self._model = model
|
||||
self._proxy_model = proxy_model
|
||||
|
||||
self._context_menu_visible = False
|
||||
|
||||
def set_model(self, model):
|
||||
self._model = model
|
||||
|
||||
def event(self, event):
|
||||
if (
|
||||
event.type() == QtCore.QEvent.KeyPress
|
||||
and event.key() == QtCore.Qt.Key_Tab
|
||||
):
|
||||
if self._model:
|
||||
find_value = self.text() + "/"
|
||||
if self._model.has_children(find_value):
|
||||
self.insert("/")
|
||||
else:
|
||||
self._completer.popup().hide()
|
||||
event.accept()
|
||||
return True
|
||||
|
||||
return super(BreadcrumbsPathInput, self).event(event)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == QtCore.Qt.Key_Escape:
|
||||
self.cancelled.emit()
|
||||
return
|
||||
|
||||
if event.key() in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter):
|
||||
self.confirmed.emit()
|
||||
return
|
||||
|
||||
super(BreadcrumbsPathInput, self).keyPressEvent(event)
|
||||
|
||||
def focusOutEvent(self, event):
|
||||
if not self._context_menu_visible:
|
||||
self.cancelled.emit()
|
||||
|
||||
self._context_menu_visible = False
|
||||
super(BreadcrumbsPathInput, self).focusOutEvent(event)
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
self._context_menu_visible = True
|
||||
super(BreadcrumbsPathInput, self).contextMenuEvent(event)
|
||||
|
||||
def _on_completer_activated(self, path):
|
||||
self.confirmed.emit()
|
||||
|
||||
def _on_text_change(self, path):
|
||||
self._proxy_model.set_path_prefix(path)
|
||||
|
||||
|
||||
class BreadcrumbsButton(QtWidgets.QToolButton):
|
||||
path_selected = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, path, model, parent):
|
||||
super(BreadcrumbsButton, self).__init__(parent)
|
||||
|
||||
self.setObjectName("BreadcrumbsButton")
|
||||
|
||||
path_prefix = path
|
||||
if path:
|
||||
path_prefix += "/"
|
||||
|
||||
self.setAutoRaise(True)
|
||||
self.setPopupMode(QtWidgets.QToolButton.MenuButtonPopup)
|
||||
|
||||
self.setMouseTracking(True)
|
||||
|
||||
if path:
|
||||
self.setText(path.split("/")[-1])
|
||||
else:
|
||||
self.setProperty("empty", "1")
|
||||
|
||||
menu = BreadcrumbsHintMenu(model, path_prefix, self)
|
||||
|
||||
self.setMenu(menu)
|
||||
|
||||
# fixed size breadcrumbs
|
||||
self.setMinimumSize(self.minimumSizeHint())
|
||||
size_policy = self.sizePolicy()
|
||||
size_policy.setVerticalPolicy(size_policy.Minimum)
|
||||
self.setSizePolicy(size_policy)
|
||||
|
||||
menu.triggered.connect(self._on_menu_click)
|
||||
self.clicked.connect(self._on_click)
|
||||
|
||||
self._path = path
|
||||
self._path_prefix = path_prefix
|
||||
self._model = model
|
||||
self._menu = menu
|
||||
|
||||
def _on_click(self):
|
||||
self.path_selected.emit(self._path)
|
||||
|
||||
def _on_menu_click(self, action):
|
||||
item = action.data()
|
||||
self.path_selected.emit(item)
|
||||
|
||||
|
||||
class BreadcrumbsAddressBar(QtWidgets.QFrame):
|
||||
"Windows Explorer-like address bar"
|
||||
path_changed = QtCore.Signal(str)
|
||||
path_edited = QtCore.Signal(str)
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super(BreadcrumbsAddressBar, self).__init__(parent)
|
||||
|
||||
self.setAutoFillBackground(True)
|
||||
self.setFrameShape(self.StyledPanel)
|
||||
|
||||
# Edit presented path textually
|
||||
proxy_model = BreadcrumbsProxy()
|
||||
path_input = BreadcrumbsPathInput(None, proxy_model, self)
|
||||
path_input.setVisible(False)
|
||||
|
||||
path_input.cancelled.connect(self._on_input_cancel)
|
||||
path_input.confirmed.connect(self._on_input_confirm)
|
||||
|
||||
# Container for `crumbs_panel`
|
||||
crumbs_container = QtWidgets.QWidget(self)
|
||||
|
||||
# Container for breadcrumbs
|
||||
crumbs_panel = QtWidgets.QWidget(crumbs_container)
|
||||
crumbs_panel.setObjectName("BreadcrumbsPanel")
|
||||
|
||||
crumbs_layout = QtWidgets.QHBoxLayout()
|
||||
crumbs_layout.setContentsMargins(0, 0, 0, 0)
|
||||
crumbs_layout.setSpacing(0)
|
||||
|
||||
crumbs_cont_layout = QtWidgets.QHBoxLayout(crumbs_container)
|
||||
crumbs_cont_layout.setContentsMargins(0, 0, 0, 0)
|
||||
crumbs_cont_layout.setSpacing(0)
|
||||
crumbs_cont_layout.addWidget(crumbs_panel)
|
||||
|
||||
# Clicking on empty space to the right puts the bar into edit mode
|
||||
switch_space = ClickableWidget(self)
|
||||
|
||||
crumb_panel_layout = QtWidgets.QHBoxLayout(crumbs_panel)
|
||||
crumb_panel_layout.setContentsMargins(0, 0, 0, 0)
|
||||
crumb_panel_layout.setSpacing(0)
|
||||
crumb_panel_layout.addLayout(crumbs_layout, 0)
|
||||
crumb_panel_layout.addWidget(switch_space, 1)
|
||||
|
||||
switch_space.clicked.connect(self.switch_space_mouse_up)
|
||||
|
||||
layout = QtWidgets.QHBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
layout.addWidget(path_input)
|
||||
layout.addWidget(crumbs_container)
|
||||
|
||||
self.setMaximumHeight(path_input.height())
|
||||
|
||||
self.crumbs_layout = crumbs_layout
|
||||
self.crumbs_panel = crumbs_panel
|
||||
self.switch_space = switch_space
|
||||
self.path_input = path_input
|
||||
self.crumbs_container = crumbs_container
|
||||
|
||||
self._model = None
|
||||
self._proxy_model = proxy_model
|
||||
|
||||
self._current_path = None
|
||||
|
||||
def set_model(self, model):
|
||||
self._model = model
|
||||
self.path_input.set_model(model)
|
||||
self._proxy_model.setSourceModel(model)
|
||||
|
||||
def _on_input_confirm(self):
|
||||
self.change_path(self.path_input.text())
|
||||
|
||||
def _on_input_cancel(self):
|
||||
self._cancel_edit()
|
||||
|
||||
def _clear_crumbs(self):
|
||||
while self.crumbs_layout.count():
|
||||
widget = self.crumbs_layout.takeAt(0).widget()
|
||||
if widget:
|
||||
widget.deleteLater()
|
||||
|
||||
def _insert_crumb(self, path):
|
||||
btn = BreadcrumbsButton(path, self._proxy_model, self.crumbs_panel)
|
||||
|
||||
self.crumbs_layout.insertWidget(0, btn)
|
||||
|
||||
btn.path_selected.connect(self._on_crumb_clicked)
|
||||
|
||||
def _on_crumb_clicked(self, path):
|
||||
"Breadcrumb was clicked"
|
||||
self.change_path(path)
|
||||
|
||||
def change_path(self, path):
|
||||
if self._model and not self._model.is_valid_path(path):
|
||||
self._show_address_field()
|
||||
else:
|
||||
self.set_path(path)
|
||||
self.path_edited.emit(path)
|
||||
|
||||
def set_path(self, path):
|
||||
if path is None or path == ".":
|
||||
path = self._current_path
|
||||
|
||||
# exit edit mode
|
||||
self._cancel_edit()
|
||||
|
||||
self._clear_crumbs()
|
||||
self._current_path = path
|
||||
self.path_input.setText(path)
|
||||
path_items = [
|
||||
item
|
||||
for item in path.split("/")
|
||||
if item
|
||||
]
|
||||
while path_items:
|
||||
item = "/".join(path_items)
|
||||
self._insert_crumb(item)
|
||||
path_items.pop(-1)
|
||||
self._insert_crumb("")
|
||||
|
||||
self.path_changed.emit(self._current_path)
|
||||
|
||||
def _cancel_edit(self):
|
||||
"Set edit line text back to current path and switch to view mode"
|
||||
# revert path
|
||||
self.path_input.setText(self.path())
|
||||
# switch back to breadcrumbs view
|
||||
self._show_address_field(False)
|
||||
|
||||
def path(self):
|
||||
"Get path displayed in this BreadcrumbsAddressBar"
|
||||
return self._current_path
|
||||
|
||||
def switch_space_mouse_up(self):
|
||||
"EVENT: switch_space mouse clicked"
|
||||
self._show_address_field(True)
|
||||
|
||||
def _show_address_field(self, show=True):
|
||||
"Show text address field"
|
||||
self.crumbs_container.setVisible(not show)
|
||||
self.path_input.setVisible(show)
|
||||
if show:
|
||||
self.path_input.setFocus()
|
||||
self.path_input.selectAll()
|
||||
|
||||
def minimumSizeHint(self):
|
||||
result = super(BreadcrumbsAddressBar, self).minimumSizeHint()
|
||||
result.setHeight(self.path_input.minimumSizeHint().height())
|
||||
return result
|
||||
|
|
@ -31,6 +31,11 @@ from openpype.settings.entities import (
|
|||
|
||||
from openpype.settings import SaveWarningExc
|
||||
from .widgets import ProjectListWidget
|
||||
from .breadcrumbs_widget import (
|
||||
BreadcrumbsAddressBar,
|
||||
SystemSettingsBreadcrumbs,
|
||||
ProjectSettingsBreadcrumbs
|
||||
)
|
||||
|
||||
from .base import GUIWidget
|
||||
from .list_item_widget import ListWidget
|
||||
|
|
@ -175,6 +180,16 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
scroll_widget = QtWidgets.QScrollArea(self)
|
||||
scroll_widget.setObjectName("GroupWidget")
|
||||
content_widget = QtWidgets.QWidget(scroll_widget)
|
||||
|
||||
breadcrumbs_label = QtWidgets.QLabel("Path:", content_widget)
|
||||
breadcrumbs_widget = BreadcrumbsAddressBar(content_widget)
|
||||
|
||||
breadcrumbs_layout = QtWidgets.QHBoxLayout()
|
||||
breadcrumbs_layout.setContentsMargins(5, 5, 5, 5)
|
||||
breadcrumbs_layout.setSpacing(5)
|
||||
breadcrumbs_layout.addWidget(breadcrumbs_label)
|
||||
breadcrumbs_layout.addWidget(breadcrumbs_widget)
|
||||
|
||||
content_layout = QtWidgets.QVBoxLayout(content_widget)
|
||||
content_layout.setContentsMargins(3, 3, 3, 3)
|
||||
content_layout.setSpacing(5)
|
||||
|
|
@ -183,40 +198,43 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
scroll_widget.setWidgetResizable(True)
|
||||
scroll_widget.setWidget(content_widget)
|
||||
|
||||
configurations_widget = QtWidgets.QWidget(self)
|
||||
|
||||
footer_widget = QtWidgets.QWidget(configurations_widget)
|
||||
footer_layout = QtWidgets.QHBoxLayout(footer_widget)
|
||||
|
||||
refresh_icon = qtawesome.icon("fa.refresh", color="white")
|
||||
refresh_btn = QtWidgets.QPushButton(footer_widget)
|
||||
refresh_btn = QtWidgets.QPushButton(self)
|
||||
refresh_btn.setIcon(refresh_icon)
|
||||
|
||||
footer_layout.addWidget(refresh_btn, 0)
|
||||
|
||||
footer_layout = QtWidgets.QHBoxLayout()
|
||||
if self.user_role == "developer":
|
||||
self._add_developer_ui(footer_layout)
|
||||
|
||||
save_btn = QtWidgets.QPushButton("Save", footer_widget)
|
||||
require_restart_label = QtWidgets.QLabel(footer_widget)
|
||||
save_btn = QtWidgets.QPushButton("Save", self)
|
||||
require_restart_label = QtWidgets.QLabel(self)
|
||||
require_restart_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
|
||||
footer_layout.addWidget(refresh_btn, 0)
|
||||
footer_layout.addWidget(require_restart_label, 1)
|
||||
footer_layout.addWidget(save_btn, 0)
|
||||
|
||||
configurations_layout = QtWidgets.QVBoxLayout(configurations_widget)
|
||||
configurations_layout = QtWidgets.QVBoxLayout()
|
||||
configurations_layout.setContentsMargins(0, 0, 0, 0)
|
||||
configurations_layout.setSpacing(0)
|
||||
|
||||
configurations_layout.addWidget(scroll_widget, 1)
|
||||
configurations_layout.addWidget(footer_widget, 0)
|
||||
configurations_layout.addLayout(footer_layout, 0)
|
||||
|
||||
main_layout = QtWidgets.QHBoxLayout(self)
|
||||
conf_wrapper_layout = QtWidgets.QHBoxLayout()
|
||||
conf_wrapper_layout.setContentsMargins(0, 0, 0, 0)
|
||||
conf_wrapper_layout.setSpacing(0)
|
||||
conf_wrapper_layout.addLayout(configurations_layout, 1)
|
||||
|
||||
main_layout = QtWidgets.QVBoxLayout(self)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_layout.setSpacing(0)
|
||||
main_layout.addWidget(configurations_widget, 1)
|
||||
main_layout.addLayout(breadcrumbs_layout, 0)
|
||||
main_layout.addLayout(conf_wrapper_layout, 1)
|
||||
|
||||
save_btn.clicked.connect(self._save)
|
||||
refresh_btn.clicked.connect(self._on_refresh)
|
||||
breadcrumbs_widget.path_edited.connect(self._on_path_edit)
|
||||
|
||||
self.save_btn = save_btn
|
||||
self.refresh_btn = refresh_btn
|
||||
|
|
@ -224,7 +242,9 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
self.scroll_widget = scroll_widget
|
||||
self.content_layout = content_layout
|
||||
self.content_widget = content_widget
|
||||
self.configurations_widget = configurations_widget
|
||||
self.breadcrumbs_widget = breadcrumbs_widget
|
||||
self.breadcrumbs_model = None
|
||||
self.conf_wrapper_layout = conf_wrapper_layout
|
||||
self.main_layout = main_layout
|
||||
|
||||
self.ui_tweaks()
|
||||
|
|
@ -232,6 +252,23 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
def ui_tweaks(self):
|
||||
return
|
||||
|
||||
def _on_path_edit(self, path):
|
||||
for input_field in self.input_fields:
|
||||
if input_field.make_sure_is_visible(path, True):
|
||||
break
|
||||
|
||||
def scroll_to(self, widget):
|
||||
if widget:
|
||||
# Process events which happened before ensurence
|
||||
# - that is because some widgets could be not visible before
|
||||
# this method was called and have incorrect size
|
||||
QtWidgets.QApplication.processEvents()
|
||||
# Scroll to widget
|
||||
self.scroll_widget.ensureWidgetVisible(widget)
|
||||
|
||||
def set_path(self, path):
|
||||
self.breadcrumbs_widget.set_path(path)
|
||||
|
||||
def _add_developer_ui(self, footer_layout):
|
||||
modify_defaults_widget = QtWidgets.QWidget()
|
||||
modify_defaults_checkbox = QtWidgets.QCheckBox(modify_defaults_widget)
|
||||
|
|
@ -427,10 +464,19 @@ class SettingsCategoryWidget(QtWidgets.QWidget):
|
|||
def _on_reset_crash(self):
|
||||
self.save_btn.setEnabled(False)
|
||||
|
||||
if self.breadcrumbs_model is not None:
|
||||
self.breadcrumbs_model.set_entity(None)
|
||||
|
||||
def _on_reset_success(self):
|
||||
if not self.save_btn.isEnabled():
|
||||
self.save_btn.setEnabled(True)
|
||||
|
||||
if self.breadcrumbs_model is not None:
|
||||
path = self.breadcrumbs_widget.path()
|
||||
self.breadcrumbs_widget.set_path("")
|
||||
self.breadcrumbs_model.set_entity(self.entity)
|
||||
self.breadcrumbs_widget.change_path(path)
|
||||
|
||||
def add_children_gui(self):
|
||||
for child_obj in self.entity.children:
|
||||
item = self.create_ui_for_entity(self, child_obj, self)
|
||||
|
|
@ -521,6 +567,10 @@ class SystemWidget(SettingsCategoryWidget):
|
|||
self.modify_defaults_checkbox.setChecked(True)
|
||||
self.modify_defaults_checkbox.setEnabled(False)
|
||||
|
||||
def ui_tweaks(self):
|
||||
self.breadcrumbs_model = SystemSettingsBreadcrumbs()
|
||||
self.breadcrumbs_widget.set_model(self.breadcrumbs_model)
|
||||
|
||||
def _on_modify_defaults(self):
|
||||
if self.modify_defaults_checkbox.isChecked():
|
||||
if not self.entity.is_in_defaults_state():
|
||||
|
|
@ -535,9 +585,12 @@ class ProjectWidget(SettingsCategoryWidget):
|
|||
self.project_name = None
|
||||
|
||||
def ui_tweaks(self):
|
||||
self.breadcrumbs_model = ProjectSettingsBreadcrumbs()
|
||||
self.breadcrumbs_widget.set_model(self.breadcrumbs_model)
|
||||
|
||||
project_list_widget = ProjectListWidget(self)
|
||||
|
||||
self.main_layout.insertWidget(0, project_list_widget, 0)
|
||||
self.conf_wrapper_layout.insertWidget(0, project_list_widget, 0)
|
||||
|
||||
project_list_widget.project_changed.connect(self._on_project_change)
|
||||
|
||||
|
|
|
|||
|
|
@ -213,6 +213,26 @@ class DictConditionalWidget(BaseWidget):
|
|||
else:
|
||||
body_widget.hide_toolbox(hide_content=False)
|
||||
|
||||
def make_sure_is_visible(self, path, scroll_to):
|
||||
if not path:
|
||||
return False
|
||||
|
||||
entity_path = self.entity.path
|
||||
if entity_path == path:
|
||||
self.set_focus(scroll_to)
|
||||
return True
|
||||
|
||||
if not path.startswith(entity_path):
|
||||
return False
|
||||
|
||||
if self.body_widget and not self.body_widget.is_expanded():
|
||||
self.body_widget.toggle_content(True)
|
||||
|
||||
for input_field in self.input_fields:
|
||||
if input_field.make_sure_is_visible(path, scroll_to):
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_widget_to_layout(self, widget, label=None):
|
||||
if not widget.entity:
|
||||
map_id = widget.id
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
from uuid import uuid4
|
||||
|
||||
from Qt import QtWidgets, QtCore
|
||||
from Qt import QtWidgets, QtCore, QtGui
|
||||
|
||||
from .base import BaseWidget
|
||||
from .widgets import (
|
||||
ExpandingWidget,
|
||||
IconButton,
|
||||
SpacerWidget
|
||||
IconButton
|
||||
)
|
||||
from openpype.tools.settings import (
|
||||
BTN_FIXED_SIZE,
|
||||
|
|
@ -15,6 +14,69 @@ from openpype.tools.settings import (
|
|||
from openpype.settings.constants import KEY_REGEX
|
||||
|
||||
|
||||
KEY_INPUT_TOOLTIP = (
|
||||
"Keys can't be duplicated and may contain alphabetical character (a-Z)"
|
||||
"\nnumerical characters (0-9) dash (\"-\") or underscore (\"_\")."
|
||||
)
|
||||
|
||||
|
||||
class PaintHelper:
|
||||
cached_icons = {}
|
||||
|
||||
@classmethod
|
||||
def _draw_image(cls, width, height, brush):
|
||||
image = QtGui.QPixmap(width, height)
|
||||
image.fill(QtCore.Qt.transparent)
|
||||
|
||||
icon_path_stroker = QtGui.QPainterPathStroker()
|
||||
icon_path_stroker.setCapStyle(QtCore.Qt.RoundCap)
|
||||
icon_path_stroker.setJoinStyle(QtCore.Qt.RoundJoin)
|
||||
icon_path_stroker.setWidth(height / 5)
|
||||
|
||||
painter = QtGui.QPainter(image)
|
||||
painter.setPen(QtCore.Qt.transparent)
|
||||
painter.setBrush(brush)
|
||||
rect = QtCore.QRect(0, 0, image.width(), image.height())
|
||||
fifteenth = rect.height() / 15
|
||||
# Left point
|
||||
p1 = QtCore.QPoint(
|
||||
rect.x() + (5 * fifteenth),
|
||||
rect.y() + (9 * fifteenth)
|
||||
)
|
||||
# Middle bottom point
|
||||
p2 = QtCore.QPoint(
|
||||
rect.center().x(),
|
||||
rect.y() + (11 * fifteenth)
|
||||
)
|
||||
# Top right point
|
||||
p3 = QtCore.QPoint(
|
||||
rect.x() + (10 * fifteenth),
|
||||
rect.y() + (5 * fifteenth)
|
||||
)
|
||||
|
||||
path = QtGui.QPainterPath(p1)
|
||||
path.lineTo(p2)
|
||||
path.lineTo(p3)
|
||||
|
||||
stroked_path = icon_path_stroker.createStroke(path)
|
||||
painter.drawPath(stroked_path)
|
||||
|
||||
painter.end()
|
||||
|
||||
return image
|
||||
|
||||
@classmethod
|
||||
def get_confirm_icon(cls, width, height):
|
||||
key = "{}x{}-confirm_image".format(width, height)
|
||||
icon = cls.cached_icons.get(key)
|
||||
|
||||
if icon is None:
|
||||
image = cls._draw_image(width, height, QtCore.Qt.white)
|
||||
icon = QtGui.QIcon(image)
|
||||
cls.cached_icons[key] = icon
|
||||
return icon
|
||||
|
||||
|
||||
def create_add_btn(parent):
|
||||
add_btn = QtWidgets.QPushButton("+", parent)
|
||||
add_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
|
||||
|
|
@ -31,6 +93,19 @@ def create_remove_btn(parent):
|
|||
return remove_btn
|
||||
|
||||
|
||||
def create_confirm_btn(parent):
|
||||
confirm_btn = QtWidgets.QPushButton(parent)
|
||||
|
||||
icon = PaintHelper.get_confirm_icon(
|
||||
BTN_FIXED_SIZE, BTN_FIXED_SIZE
|
||||
)
|
||||
confirm_btn.setIcon(icon)
|
||||
confirm_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
|
||||
confirm_btn.setProperty("btn-type", "tool-item")
|
||||
confirm_btn.setFixedSize(BTN_FIXED_SIZE, BTN_FIXED_SIZE)
|
||||
return confirm_btn
|
||||
|
||||
|
||||
class ModifiableDictEmptyItem(QtWidgets.QWidget):
|
||||
def __init__(self, entity_widget, store_as_list, parent):
|
||||
super(ModifiableDictEmptyItem, self).__init__(parent)
|
||||
|
|
@ -42,6 +117,8 @@ class ModifiableDictEmptyItem(QtWidgets.QWidget):
|
|||
self.is_duplicated = False
|
||||
self.key_is_valid = store_as_list
|
||||
|
||||
self.confirm_btn = None
|
||||
|
||||
if self.collapsible_key:
|
||||
self.create_collapsible_ui()
|
||||
else:
|
||||
|
|
@ -61,7 +138,6 @@ class ModifiableDictEmptyItem(QtWidgets.QWidget):
|
|||
def create_addible_ui(self):
|
||||
add_btn = create_add_btn(self)
|
||||
remove_btn = create_remove_btn(self)
|
||||
spacer_widget = SpacerWidget(self)
|
||||
|
||||
remove_btn.setEnabled(False)
|
||||
|
||||
|
|
@ -70,13 +146,12 @@ class ModifiableDictEmptyItem(QtWidgets.QWidget):
|
|||
layout.setSpacing(3)
|
||||
layout.addWidget(add_btn, 0)
|
||||
layout.addWidget(remove_btn, 0)
|
||||
layout.addWidget(spacer_widget, 1)
|
||||
layout.addStretch(1)
|
||||
|
||||
add_btn.clicked.connect(self._on_add_clicked)
|
||||
|
||||
self.add_btn = add_btn
|
||||
self.remove_btn = remove_btn
|
||||
self.spacer_widget = spacer_widget
|
||||
|
||||
def _on_focus_lose(self):
|
||||
if self.key_input.hasFocus() or self.key_label_input.hasFocus():
|
||||
|
|
@ -111,7 +186,16 @@ class ModifiableDictEmptyItem(QtWidgets.QWidget):
|
|||
self.is_duplicated = self.entity_widget.is_key_duplicated(key)
|
||||
key_input_state = ""
|
||||
# Collapsible key and empty key are not invalid
|
||||
if self.collapsible_key and self.key_input.text() == "":
|
||||
key_value = self.key_input.text()
|
||||
if self.confirm_btn is not None:
|
||||
conf_disabled = (
|
||||
key_value == ""
|
||||
or not self.key_is_valid
|
||||
or self.is_duplicated
|
||||
)
|
||||
self.confirm_btn.setEnabled(not conf_disabled)
|
||||
|
||||
if self.collapsible_key and key_value == "":
|
||||
pass
|
||||
elif self.is_duplicated or not self.key_is_valid:
|
||||
key_input_state = "invalid"
|
||||
|
|
@ -124,6 +208,7 @@ class ModifiableDictEmptyItem(QtWidgets.QWidget):
|
|||
def create_collapsible_ui(self):
|
||||
key_input = QtWidgets.QLineEdit(self)
|
||||
key_input.setObjectName("DictKey")
|
||||
key_input.setToolTip(KEY_INPUT_TOOLTIP)
|
||||
|
||||
key_label_input = QtWidgets.QLineEdit(self)
|
||||
|
||||
|
|
@ -141,11 +226,15 @@ class ModifiableDictEmptyItem(QtWidgets.QWidget):
|
|||
key_input_label_widget = QtWidgets.QLabel("Key:", self)
|
||||
key_label_input_label_widget = QtWidgets.QLabel("Label:", self)
|
||||
|
||||
confirm_btn = create_confirm_btn(self)
|
||||
confirm_btn.setEnabled(False)
|
||||
|
||||
wrapper_widget = ExpandingWidget("", self)
|
||||
wrapper_widget.add_widget_after_label(key_input_label_widget)
|
||||
wrapper_widget.add_widget_after_label(key_input)
|
||||
wrapper_widget.add_widget_after_label(key_label_input_label_widget)
|
||||
wrapper_widget.add_widget_after_label(key_label_input)
|
||||
wrapper_widget.add_widget_after_label(confirm_btn)
|
||||
wrapper_widget.hide_toolbox()
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
|
|
@ -157,9 +246,12 @@ class ModifiableDictEmptyItem(QtWidgets.QWidget):
|
|||
key_input.returnPressed.connect(self._on_enter_press)
|
||||
key_label_input.returnPressed.connect(self._on_enter_press)
|
||||
|
||||
confirm_btn.clicked.connect(self._on_enter_press)
|
||||
|
||||
self.key_input = key_input
|
||||
self.key_label_input = key_label_input
|
||||
self.wrapper_widget = wrapper_widget
|
||||
self.confirm_btn = confirm_btn
|
||||
|
||||
|
||||
class ModifiableDictItem(QtWidgets.QWidget):
|
||||
|
|
@ -190,10 +282,14 @@ class ModifiableDictItem(QtWidgets.QWidget):
|
|||
|
||||
self.key_label_input = None
|
||||
|
||||
self.confirm_btn = None
|
||||
|
||||
if collapsible_key:
|
||||
self.create_collapsible_ui()
|
||||
else:
|
||||
self.create_addible_ui()
|
||||
|
||||
self.key_input.setToolTip(KEY_INPUT_TOOLTIP)
|
||||
self.update_style()
|
||||
|
||||
@property
|
||||
|
|
@ -277,6 +373,9 @@ class ModifiableDictItem(QtWidgets.QWidget):
|
|||
edit_btn.setProperty("btn-type", "tool-item-icon")
|
||||
edit_btn.setFixedHeight(BTN_FIXED_SIZE)
|
||||
|
||||
confirm_btn = create_confirm_btn(self)
|
||||
confirm_btn.setVisible(False)
|
||||
|
||||
remove_btn = create_remove_btn(self)
|
||||
|
||||
key_input_label_widget = QtWidgets.QLabel("Key:")
|
||||
|
|
@ -286,6 +385,7 @@ class ModifiableDictItem(QtWidgets.QWidget):
|
|||
wrapper_widget.add_widget_after_label(key_input)
|
||||
wrapper_widget.add_widget_after_label(key_label_input_label_widget)
|
||||
wrapper_widget.add_widget_after_label(key_label_input)
|
||||
wrapper_widget.add_widget_after_label(confirm_btn)
|
||||
wrapper_widget.add_widget_after_label(remove_btn)
|
||||
|
||||
key_input.textChanged.connect(self._on_key_change)
|
||||
|
|
@ -295,6 +395,7 @@ class ModifiableDictItem(QtWidgets.QWidget):
|
|||
key_label_input.returnPressed.connect(self._on_enter_press)
|
||||
|
||||
edit_btn.clicked.connect(self.on_edit_pressed)
|
||||
confirm_btn.clicked.connect(self._on_enter_press)
|
||||
remove_btn.clicked.connect(self.on_remove_clicked)
|
||||
|
||||
# Hide edit inputs
|
||||
|
|
@ -310,6 +411,7 @@ class ModifiableDictItem(QtWidgets.QWidget):
|
|||
self.key_label_input_label_widget = key_label_input_label_widget
|
||||
self.wrapper_widget = wrapper_widget
|
||||
self.edit_btn = edit_btn
|
||||
self.confirm_btn = confirm_btn
|
||||
self.remove_btn = remove_btn
|
||||
|
||||
self.content_widget = content_widget
|
||||
|
|
@ -319,6 +421,9 @@ class ModifiableDictItem(QtWidgets.QWidget):
|
|||
self.category_widget, self.entity, self
|
||||
)
|
||||
|
||||
def make_sure_is_visible(self, *args, **kwargs):
|
||||
return self.input_field.make_sure_is_visible(*args, **kwargs)
|
||||
|
||||
def get_style_state(self):
|
||||
if self.is_invalid:
|
||||
return "invalid"
|
||||
|
|
@ -415,6 +520,14 @@ class ModifiableDictItem(QtWidgets.QWidget):
|
|||
self.temp_key, key, self
|
||||
)
|
||||
self.temp_key = key
|
||||
if self.confirm_btn is not None:
|
||||
conf_disabled = (
|
||||
key == ""
|
||||
or not self.key_is_valid
|
||||
or is_key_duplicated
|
||||
)
|
||||
self.confirm_btn.setEnabled(not conf_disabled)
|
||||
|
||||
if is_key_duplicated or not self.key_is_valid:
|
||||
return
|
||||
|
||||
|
|
@ -434,7 +547,7 @@ class ModifiableDictItem(QtWidgets.QWidget):
|
|||
key_value = self.key_input.text()
|
||||
key_label_value = self.key_label_input.text()
|
||||
if key_label_value:
|
||||
label = "{} ({})".format(key_label_value, key_value)
|
||||
label = "{} ({})".format(key_value, key_label_value)
|
||||
else:
|
||||
label = key_value
|
||||
self.wrapper_widget.label_widget.setText(label)
|
||||
|
|
@ -457,6 +570,7 @@ class ModifiableDictItem(QtWidgets.QWidget):
|
|||
self.key_input.setVisible(enabled)
|
||||
self.key_input_label_widget.setVisible(enabled)
|
||||
self.key_label_input.setVisible(enabled)
|
||||
self.confirm_btn.setVisible(enabled)
|
||||
if not self.is_required:
|
||||
self.remove_btn.setVisible(enabled)
|
||||
if enabled:
|
||||
|
|
@ -681,10 +795,6 @@ class DictMutableKeysWidget(BaseWidget):
|
|||
def remove_key(self, widget):
|
||||
key = self.entity.get_child_key(widget.entity)
|
||||
self.entity.pop(key)
|
||||
# Poping of key from entity should remove the entity and input field.
|
||||
# this is kept for testing purposes.
|
||||
if widget in self.input_fields:
|
||||
self.remove_row(widget)
|
||||
|
||||
def change_key(self, new_key, widget):
|
||||
if not new_key or widget.is_key_duplicated:
|
||||
|
|
@ -751,6 +861,11 @@ class DictMutableKeysWidget(BaseWidget):
|
|||
return input_field
|
||||
|
||||
def remove_row(self, widget):
|
||||
if widget.is_key_duplicated:
|
||||
new_key = widget.uuid_key
|
||||
if new_key is None:
|
||||
new_key = str(uuid4())
|
||||
self.validate_key_duplication(widget.temp_key, new_key, widget)
|
||||
self.input_fields.remove(widget)
|
||||
self.content_layout.removeWidget(widget)
|
||||
widget.deleteLater()
|
||||
|
|
@ -834,7 +949,10 @@ class DictMutableKeysWidget(BaseWidget):
|
|||
_input_field.set_entity_value()
|
||||
|
||||
else:
|
||||
if input_field.key_value() != key:
|
||||
if (
|
||||
not input_field.is_key_duplicated
|
||||
and input_field.key_value() != key
|
||||
):
|
||||
changed = True
|
||||
input_field.set_key(key)
|
||||
|
||||
|
|
@ -846,6 +964,26 @@ class DictMutableKeysWidget(BaseWidget):
|
|||
if changed:
|
||||
self.on_shuffle()
|
||||
|
||||
def make_sure_is_visible(self, path, scroll_to):
|
||||
if not path:
|
||||
return False
|
||||
|
||||
entity_path = self.entity.path
|
||||
if entity_path == path:
|
||||
self.set_focus(scroll_to)
|
||||
return True
|
||||
|
||||
if not path.startswith(entity_path):
|
||||
return False
|
||||
|
||||
if self.body_widget and not self.body_widget.is_expanded():
|
||||
self.body_widget.toggle_content(True)
|
||||
|
||||
for input_field in self.input_fields:
|
||||
if input_field.make_sure_is_visible(path, scroll_to):
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_entity_value(self):
|
||||
while self.input_fields:
|
||||
self.remove_row(self.input_fields[0])
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ from .widgets import (
|
|||
ExpandingWidget,
|
||||
NumberSpinBox,
|
||||
GridLabelWidget,
|
||||
ComboBox,
|
||||
NiceCheckbox
|
||||
SettingsComboBox,
|
||||
NiceCheckbox,
|
||||
SettingsPlainTextEdit,
|
||||
SettingsLineEdit
|
||||
)
|
||||
from .multiselection_combobox import MultiSelectionComboBox
|
||||
from .wrapper_widgets import (
|
||||
|
|
@ -46,6 +48,7 @@ class DictImmutableKeysWidget(BaseWidget):
|
|||
self._ui_item_base()
|
||||
label = self.entity.label
|
||||
|
||||
self._direct_children_widgets = []
|
||||
self._parent_widget_by_entity_id = {}
|
||||
self._added_wrapper_ids = set()
|
||||
self._prepare_entity_layouts(
|
||||
|
|
@ -154,9 +157,41 @@ class DictImmutableKeysWidget(BaseWidget):
|
|||
else:
|
||||
body_widget.hide_toolbox(hide_content=False)
|
||||
|
||||
def make_sure_is_visible(self, path, scroll_to):
|
||||
if not path:
|
||||
return False
|
||||
|
||||
entity_path = self.entity.path
|
||||
if entity_path == path:
|
||||
self.set_focus(scroll_to)
|
||||
return True
|
||||
|
||||
if not path.startswith(entity_path):
|
||||
return False
|
||||
|
||||
is_checkbox_child = False
|
||||
changed = False
|
||||
for direct_child in self._direct_children_widgets:
|
||||
if direct_child.make_sure_is_visible(path, scroll_to):
|
||||
changed = True
|
||||
if direct_child.entity is self.checkbox_child:
|
||||
is_checkbox_child = True
|
||||
break
|
||||
|
||||
# Change scroll to this widget
|
||||
if is_checkbox_child:
|
||||
self.scroll_to(self)
|
||||
|
||||
elif self.body_widget and not self.body_widget.is_expanded():
|
||||
# Expand widget if is callapsible
|
||||
self.body_widget.toggle_content(True)
|
||||
|
||||
return changed
|
||||
|
||||
def add_widget_to_layout(self, widget, label=None):
|
||||
if self.checkbox_child and widget.entity is self.checkbox_child:
|
||||
self.body_widget.add_widget_before_label(widget)
|
||||
self._direct_children_widgets.append(widget)
|
||||
return
|
||||
|
||||
if not widget.entity:
|
||||
|
|
@ -172,6 +207,8 @@ class DictImmutableKeysWidget(BaseWidget):
|
|||
self._added_wrapper_ids.add(wrapper.id)
|
||||
return
|
||||
|
||||
self._direct_children_widgets.append(widget)
|
||||
|
||||
row = self.content_layout.rowCount()
|
||||
if not label or isinstance(widget, WrapperWidget):
|
||||
self.content_layout.addWidget(widget, row, 0, 1, 2)
|
||||
|
|
@ -270,11 +307,8 @@ class BoolWidget(InputWidget):
|
|||
height=checkbox_height, parent=self.content_widget
|
||||
)
|
||||
|
||||
spacer = QtWidgets.QWidget(self.content_widget)
|
||||
spacer.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
|
||||
self.content_layout.addWidget(self.input_field, 0)
|
||||
self.content_layout.addWidget(spacer, 1)
|
||||
self.content_layout.addStretch(1)
|
||||
|
||||
self.setFocusProxy(self.input_field)
|
||||
|
||||
|
|
@ -297,9 +331,9 @@ class TextWidget(InputWidget):
|
|||
def _add_inputs_to_layout(self):
|
||||
multiline = self.entity.multiline
|
||||
if multiline:
|
||||
self.input_field = QtWidgets.QPlainTextEdit(self.content_widget)
|
||||
self.input_field = SettingsPlainTextEdit(self.content_widget)
|
||||
else:
|
||||
self.input_field = QtWidgets.QLineEdit(self.content_widget)
|
||||
self.input_field = SettingsLineEdit(self.content_widget)
|
||||
|
||||
placeholder_text = self.entity.placeholder_text
|
||||
if placeholder_text:
|
||||
|
|
@ -313,8 +347,12 @@ class TextWidget(InputWidget):
|
|||
|
||||
self.content_layout.addWidget(self.input_field, 1, **layout_kwargs)
|
||||
|
||||
self.input_field.focused_in.connect(self._on_input_focus)
|
||||
self.input_field.textChanged.connect(self._on_value_change)
|
||||
|
||||
def _on_input_focus(self):
|
||||
self.focused_in()
|
||||
|
||||
def _on_entity_change(self):
|
||||
if self.entity.value != self.input_value():
|
||||
self.set_entity_value()
|
||||
|
|
@ -352,6 +390,10 @@ class NumberWidget(InputWidget):
|
|||
self.content_layout.addWidget(self.input_field, 1)
|
||||
|
||||
self.input_field.valueChanged.connect(self._on_value_change)
|
||||
self.input_field.focused_in.connect(self._on_input_focus)
|
||||
|
||||
def _on_input_focus(self):
|
||||
self.focused_in()
|
||||
|
||||
def _on_entity_change(self):
|
||||
if self.entity.value != self.input_field.value():
|
||||
|
|
@ -366,7 +408,7 @@ class NumberWidget(InputWidget):
|
|||
self.entity.set(self.input_field.value())
|
||||
|
||||
|
||||
class RawJsonInput(QtWidgets.QPlainTextEdit):
|
||||
class RawJsonInput(SettingsPlainTextEdit):
|
||||
tab_length = 4
|
||||
|
||||
def __init__(self, valid_type, *args, **kwargs):
|
||||
|
|
@ -428,15 +470,18 @@ class RawJsonWidget(InputWidget):
|
|||
QtWidgets.QSizePolicy.Minimum,
|
||||
QtWidgets.QSizePolicy.MinimumExpanding
|
||||
)
|
||||
|
||||
self.setFocusProxy(self.input_field)
|
||||
|
||||
self.content_layout.addWidget(
|
||||
self.input_field, 1, alignment=QtCore.Qt.AlignTop
|
||||
)
|
||||
|
||||
self.input_field.focused_in.connect(self._on_input_focus)
|
||||
self.input_field.textChanged.connect(self._on_value_change)
|
||||
|
||||
def _on_input_focus(self):
|
||||
self.focused_in()
|
||||
|
||||
def set_entity_value(self):
|
||||
self.input_field.set_value(self.entity.value)
|
||||
self._is_invalid = self.input_field.has_invalid_value()
|
||||
|
|
@ -470,7 +515,7 @@ class EnumeratorWidget(InputWidget):
|
|||
)
|
||||
|
||||
else:
|
||||
self.input_field = ComboBox(self.content_widget)
|
||||
self.input_field = SettingsComboBox(self.content_widget)
|
||||
|
||||
for enum_item in self.entity.enum_items:
|
||||
for value, label in enum_item.items():
|
||||
|
|
@ -480,8 +525,12 @@ class EnumeratorWidget(InputWidget):
|
|||
|
||||
self.setFocusProxy(self.input_field)
|
||||
|
||||
self.input_field.focused_in.connect(self._on_input_focus)
|
||||
self.input_field.value_changed.connect(self._on_value_change)
|
||||
|
||||
def _on_input_focus(self):
|
||||
self.focused_in()
|
||||
|
||||
def _on_entity_change(self):
|
||||
if self.entity.value != self.input_field.value():
|
||||
self.set_entity_value()
|
||||
|
|
@ -562,6 +611,9 @@ class PathWidget(BaseWidget):
|
|||
def set_entity_value(self):
|
||||
self.input_field.set_entity_value()
|
||||
|
||||
def make_sure_is_visible(self, *args, **kwargs):
|
||||
return self.input_field.make_sure_is_visible(*args, **kwargs)
|
||||
|
||||
def hierarchical_style_update(self):
|
||||
self.update_style()
|
||||
self.input_field.hierarchical_style_update()
|
||||
|
|
@ -632,14 +684,19 @@ class PathWidget(BaseWidget):
|
|||
|
||||
class PathInputWidget(InputWidget):
|
||||
def _add_inputs_to_layout(self):
|
||||
self.input_field = QtWidgets.QLineEdit(self.content_widget)
|
||||
self.input_field = SettingsLineEdit(self.content_widget)
|
||||
placeholder = self.entity.placeholder_text
|
||||
if placeholder:
|
||||
self.input_field.setPlaceholderText(placeholder)
|
||||
|
||||
self.setFocusProxy(self.input_field)
|
||||
self.content_layout.addWidget(self.input_field)
|
||||
|
||||
self.input_field.textChanged.connect(self._on_value_change)
|
||||
self.input_field.focused_in.connect(self._on_input_focus)
|
||||
|
||||
def _on_input_focus(self):
|
||||
self.focused_in()
|
||||
|
||||
def _on_entity_change(self):
|
||||
if self.entity.value != self.input_value():
|
||||
|
|
|
|||
|
|
@ -18,8 +18,6 @@ class EmptyListItem(QtWidgets.QWidget):
|
|||
|
||||
add_btn = QtWidgets.QPushButton("+", self)
|
||||
remove_btn = QtWidgets.QPushButton("-", self)
|
||||
spacer_widget = QtWidgets.QWidget(self)
|
||||
spacer_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
|
||||
add_btn.setFocusPolicy(QtCore.Qt.ClickFocus)
|
||||
remove_btn.setEnabled(False)
|
||||
|
|
@ -35,13 +33,12 @@ class EmptyListItem(QtWidgets.QWidget):
|
|||
layout.setSpacing(3)
|
||||
layout.addWidget(add_btn, 0)
|
||||
layout.addWidget(remove_btn, 0)
|
||||
layout.addWidget(spacer_widget, 1)
|
||||
layout.addStretch(1)
|
||||
|
||||
add_btn.clicked.connect(self._on_add_clicked)
|
||||
|
||||
self.add_btn = add_btn
|
||||
self.remove_btn = remove_btn
|
||||
self.spacer_widget = spacer_widget
|
||||
|
||||
def _on_add_clicked(self):
|
||||
self.entity_widget.add_new_item()
|
||||
|
|
@ -101,12 +98,6 @@ class ListItem(QtWidgets.QWidget):
|
|||
self.category_widget, self.entity, self
|
||||
)
|
||||
|
||||
spacer_widget = QtWidgets.QWidget(self)
|
||||
spacer_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
spacer_widget.setVisible(False)
|
||||
|
||||
layout.addWidget(spacer_widget, 1)
|
||||
|
||||
layout.addWidget(up_btn, 0)
|
||||
layout.addWidget(down_btn, 0)
|
||||
|
||||
|
|
@ -115,8 +106,6 @@ class ListItem(QtWidgets.QWidget):
|
|||
self.up_btn = up_btn
|
||||
self.down_btn = down_btn
|
||||
|
||||
self.spacer_widget = spacer_widget
|
||||
|
||||
self._row = -1
|
||||
self._is_last = False
|
||||
|
||||
|
|
@ -129,6 +118,9 @@ class ListItem(QtWidgets.QWidget):
|
|||
*args, **kwargs
|
||||
)
|
||||
|
||||
def make_sure_is_visible(self, *args, **kwargs):
|
||||
return self.input_field.make_sure_is_visible(*args, **kwargs)
|
||||
|
||||
@property
|
||||
def is_invalid(self):
|
||||
return self.input_field.is_invalid
|
||||
|
|
@ -275,6 +267,26 @@ class ListWidget(InputWidget):
|
|||
invalid.extend(input_field.get_invalid())
|
||||
return invalid
|
||||
|
||||
def make_sure_is_visible(self, path, scroll_to):
|
||||
if not path:
|
||||
return False
|
||||
|
||||
entity_path = self.entity.path
|
||||
if entity_path == path:
|
||||
self.set_focus(scroll_to)
|
||||
return True
|
||||
|
||||
if not path.startswith(entity_path):
|
||||
return False
|
||||
|
||||
if self.body_widget and not self.body_widget.is_expanded():
|
||||
self.body_widget.toggle_content(True)
|
||||
|
||||
for input_field in self.input_fields:
|
||||
if input_field.make_sure_is_visible(path, scroll_to):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _on_entity_change(self):
|
||||
# TODO do less inefficient
|
||||
childen_order = []
|
||||
|
|
|
|||
|
|
@ -65,6 +65,21 @@ class ListStrictWidget(BaseWidget):
|
|||
invalid.extend(input_field.get_invalid())
|
||||
return invalid
|
||||
|
||||
def make_sure_is_visible(self, path, scroll_to):
|
||||
if not path:
|
||||
return False
|
||||
|
||||
entity_path = self.entity.path
|
||||
if entity_path == path:
|
||||
self.set_focus(scroll_to)
|
||||
return True
|
||||
|
||||
if path.startswith(entity_path):
|
||||
for input_field in self.input_fields:
|
||||
if input_field.make_sure_is_visible(path, scroll_to):
|
||||
return True
|
||||
return False
|
||||
|
||||
def add_widget_to_layout(self, widget, label=None):
|
||||
# Horizontally added children
|
||||
if self.entity.is_horizontal:
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ class ComboItemDelegate(QtWidgets.QStyledItemDelegate):
|
|||
|
||||
class MultiSelectionComboBox(QtWidgets.QComboBox):
|
||||
value_changed = QtCore.Signal()
|
||||
focused_in = QtCore.Signal()
|
||||
|
||||
ignored_keys = {
|
||||
QtCore.Qt.Key_Up,
|
||||
QtCore.Qt.Key_Down,
|
||||
|
|
@ -56,6 +58,10 @@ class MultiSelectionComboBox(QtWidgets.QComboBox):
|
|||
self.lines = {}
|
||||
self.item_height = None
|
||||
|
||||
def focusInEvent(self, event):
|
||||
self.focused_in.emit()
|
||||
return super(MultiSelectionComboBox, self).focusInEvent(event)
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
"""Reimplemented."""
|
||||
self._popup_is_shown = False
|
||||
|
|
|
|||
|
|
@ -388,4 +388,32 @@ QTableView::item:pressed, QListView::item:pressed, QTreeView::item:pressed {
|
|||
|
||||
QTableView::item:selected:active, QTreeView::item:selected:active, QListView::item:selected:active {
|
||||
background: #3d8ec9;
|
||||
}
|
||||
}
|
||||
|
||||
#BreadcrumbsPathInput {
|
||||
padding: 2px;
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
#BreadcrumbsButton {
|
||||
padding-right: 12px;
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
#BreadcrumbsButton[empty="1"] {
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
#BreadcrumbsButton::menu-button {
|
||||
width: 12px;
|
||||
background: rgba(127, 127, 127, 60);
|
||||
}
|
||||
#BreadcrumbsButton::menu-button:hover {
|
||||
background: rgba(127, 127, 127, 90);
|
||||
}
|
||||
|
||||
#BreadcrumbsPanel {
|
||||
border: 1px solid #4e5254;
|
||||
border-radius: 5px;
|
||||
background: #21252B;;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,22 @@ from avalon.mongodb import (
|
|||
from openpype.settings.lib import get_system_settings
|
||||
|
||||
|
||||
class SettingsLineEdit(QtWidgets.QLineEdit):
|
||||
focused_in = QtCore.Signal()
|
||||
|
||||
def focusInEvent(self, event):
|
||||
super(SettingsLineEdit, self).focusInEvent(event)
|
||||
self.focused_in.emit()
|
||||
|
||||
|
||||
class SettingsPlainTextEdit(QtWidgets.QPlainTextEdit):
|
||||
focused_in = QtCore.Signal()
|
||||
|
||||
def focusInEvent(self, event):
|
||||
super(SettingsPlainTextEdit, self).focusInEvent(event)
|
||||
self.focused_in.emit()
|
||||
|
||||
|
||||
class ShadowWidget(QtWidgets.QWidget):
|
||||
def __init__(self, message, parent):
|
||||
super(ShadowWidget, self).__init__(parent)
|
||||
|
|
@ -70,6 +86,8 @@ class IconButton(QtWidgets.QPushButton):
|
|||
|
||||
|
||||
class NumberSpinBox(QtWidgets.QDoubleSpinBox):
|
||||
focused_in = QtCore.Signal()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
min_value = kwargs.pop("minimum", -99999)
|
||||
max_value = kwargs.pop("maximum", 99999)
|
||||
|
|
@ -80,6 +98,10 @@ class NumberSpinBox(QtWidgets.QDoubleSpinBox):
|
|||
self.setMinimum(min_value)
|
||||
self.setMaximum(max_value)
|
||||
|
||||
def focusInEvent(self, event):
|
||||
super(NumberSpinBox, self).focusInEvent(event)
|
||||
self.focused_in.emit()
|
||||
|
||||
def wheelEvent(self, event):
|
||||
if self.hasFocus():
|
||||
super(NumberSpinBox, self).wheelEvent(event)
|
||||
|
|
@ -93,18 +115,23 @@ class NumberSpinBox(QtWidgets.QDoubleSpinBox):
|
|||
return output
|
||||
|
||||
|
||||
class ComboBox(QtWidgets.QComboBox):
|
||||
class SettingsComboBox(QtWidgets.QComboBox):
|
||||
value_changed = QtCore.Signal()
|
||||
focused_in = QtCore.Signal()
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ComboBox, self).__init__(*args, **kwargs)
|
||||
super(SettingsComboBox, self).__init__(*args, **kwargs)
|
||||
|
||||
self.currentIndexChanged.connect(self._on_change)
|
||||
self.setFocusPolicy(QtCore.Qt.StrongFocus)
|
||||
|
||||
def wheelEvent(self, event):
|
||||
if self.hasFocus():
|
||||
return super(ComboBox, self).wheelEvent(event)
|
||||
return super(SettingsComboBox, self).wheelEvent(event)
|
||||
|
||||
def focusInEvent(self, event):
|
||||
self.focused_in.emit()
|
||||
return super(SettingsComboBox, self).focusInEvent(event)
|
||||
|
||||
def _on_change(self, *args, **kwargs):
|
||||
self.value_changed.emit()
|
||||
|
|
@ -160,15 +187,13 @@ class ExpandingWidget(QtWidgets.QWidget):
|
|||
after_label_layout = QtWidgets.QHBoxLayout(after_label_widget)
|
||||
after_label_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
||||
spacer_widget = QtWidgets.QWidget(side_line_widget)
|
||||
|
||||
side_line_layout = QtWidgets.QHBoxLayout(side_line_widget)
|
||||
side_line_layout.setContentsMargins(5, 10, 0, 10)
|
||||
side_line_layout.addWidget(button_toggle)
|
||||
side_line_layout.addWidget(before_label_widget)
|
||||
side_line_layout.addWidget(label_widget)
|
||||
side_line_layout.addWidget(after_label_widget)
|
||||
side_line_layout.addWidget(spacer_widget, 1)
|
||||
side_line_layout.addStretch(1)
|
||||
|
||||
top_part_layout = QtWidgets.QHBoxLayout(top_part)
|
||||
top_part_layout.setContentsMargins(0, 0, 0, 0)
|
||||
|
|
@ -176,7 +201,6 @@ class ExpandingWidget(QtWidgets.QWidget):
|
|||
|
||||
before_label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
after_label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
spacer_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
|
||||
|
|
@ -215,6 +239,9 @@ class ExpandingWidget(QtWidgets.QWidget):
|
|||
self.main_layout.addWidget(content_widget)
|
||||
self.content_widget = content_widget
|
||||
|
||||
def is_expanded(self):
|
||||
return self.button_toggle.isChecked()
|
||||
|
||||
def _btn_clicked(self):
|
||||
self.toggle_content(self.button_toggle.isChecked())
|
||||
|
||||
|
|
@ -341,31 +368,21 @@ class GridLabelWidget(QtWidgets.QWidget):
|
|||
|
||||
self.properties = {}
|
||||
|
||||
label_widget = QtWidgets.QLabel(label, self)
|
||||
|
||||
label_proxy_layout = QtWidgets.QHBoxLayout()
|
||||
label_proxy_layout.setContentsMargins(0, 0, 0, 0)
|
||||
label_proxy_layout.setSpacing(0)
|
||||
|
||||
label_proxy_layout.addWidget(label_widget, 0, QtCore.Qt.AlignRight)
|
||||
|
||||
layout = QtWidgets.QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 2, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
|
||||
label_proxy = QtWidgets.QWidget(self)
|
||||
layout.addLayout(label_proxy_layout, 0)
|
||||
layout.addStretch(1)
|
||||
|
||||
label_proxy_layout = QtWidgets.QHBoxLayout(label_proxy)
|
||||
label_proxy_layout.setContentsMargins(0, 0, 0, 0)
|
||||
label_proxy_layout.setSpacing(0)
|
||||
|
||||
label_widget = QtWidgets.QLabel(label, label_proxy)
|
||||
spacer_widget_h = SpacerWidget(label_proxy)
|
||||
label_proxy_layout.addWidget(
|
||||
spacer_widget_h, 0, alignment=QtCore.Qt.AlignRight
|
||||
)
|
||||
label_proxy_layout.addWidget(
|
||||
label_widget, 0, alignment=QtCore.Qt.AlignRight
|
||||
)
|
||||
|
||||
spacer_widget_v = SpacerWidget(self)
|
||||
|
||||
layout.addWidget(label_proxy, 0)
|
||||
layout.addWidget(spacer_widget_v, 1)
|
||||
|
||||
label_proxy.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
label_widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
|
||||
|
||||
self.label_widget = label_widget
|
||||
|
|
@ -380,6 +397,8 @@ class GridLabelWidget(QtWidgets.QWidget):
|
|||
|
||||
def mouseReleaseEvent(self, event):
|
||||
if self.input_field:
|
||||
if event and event.button() == QtCore.Qt.LeftButton:
|
||||
self.input_field.focused_in()
|
||||
return self.input_field.show_actions_menu(event)
|
||||
return super(GridLabelWidget, self).mouseReleaseEvent(event)
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,14 @@ class WrapperWidget(QtWidgets.QWidget):
|
|||
|
||||
self.create_ui()
|
||||
|
||||
def make_sure_is_visible(self, *args, **kwargs):
|
||||
changed = False
|
||||
for input_field in self.input_fields:
|
||||
if input_field.make_sure_is_visible(*args, **kwargs):
|
||||
changed = True
|
||||
break
|
||||
return changed
|
||||
|
||||
def create_ui(self):
|
||||
raise NotImplementedError(
|
||||
"{} does not have implemented `create_ui`.".format(
|
||||
|
|
@ -89,6 +97,14 @@ class CollapsibleWrapper(WrapperWidget):
|
|||
else:
|
||||
body_widget.hide_toolbox(hide_content=False)
|
||||
|
||||
def make_sure_is_visible(self, *args, **kwargs):
|
||||
result = super(CollapsibleWrapper, self).make_sure_is_visible(
|
||||
*args, **kwargs
|
||||
)
|
||||
if result:
|
||||
self.body_widget.toggle_content(True)
|
||||
return result
|
||||
|
||||
def add_widget_to_layout(self, widget, label=None):
|
||||
self.input_fields.append(widget)
|
||||
|
||||
|
|
|
|||
|
|
@ -50,8 +50,18 @@ function Install-Poetry() {
|
|||
Write-Host "Installing Poetry ... "
|
||||
$python = "python"
|
||||
if (Get-Command "pyenv" -ErrorAction SilentlyContinue) {
|
||||
if (-not (Test-Path -PathType Leaf -Path "$($openpype_root)\.python-version")) {
|
||||
$result = & pyenv global
|
||||
if ($result -eq "no global version configured") {
|
||||
Write-Host "!!! " -NoNewline -ForegroundColor Red
|
||||
Write-Host "Using pyenv but having no local or global version of Python set."
|
||||
Exit-WithCode 1
|
||||
}
|
||||
}
|
||||
$python = & pyenv which python
|
||||
|
||||
}
|
||||
|
||||
$env:POETRY_HOME="$openpype_root\.poetry"
|
||||
(Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | & $($python) -
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,9 +55,9 @@ def inject_openpype_environment(deadlinePlugin):
|
|||
"AVALON_TASK, AVALON_APP_NAME"
|
||||
raise RuntimeError(msg)
|
||||
|
||||
print("args::{}".format(args))
|
||||
print("args:::{}".format(args))
|
||||
|
||||
exit_code = subprocess.call(args, shell=True)
|
||||
exit_code = subprocess.call(args, cwd=os.path.dirname(openpype_app))
|
||||
if exit_code != 0:
|
||||
raise RuntimeError("Publishing failed, check worker's log")
|
||||
|
||||
|
|
|
|||
|
|
@ -87,6 +87,26 @@ When you publish your model with top group named like `foo_GRP` it will fail. Bu
|
|||
All regexes used here are in Python variant.
|
||||
:::
|
||||
|
||||
### Maya > Deadline submitter
|
||||
This plugin provides connection between Maya and Deadline. It is using [Deadline Webservice](https://docs.thinkboxsoftware.com/products/deadline/10.0/1_User%20Manual/manual/web-service.html) to submit jobs to farm.
|
||||

|
||||
|
||||
You can set various aspects of scene submission to farm with per-project settings in **Setting UI**.
|
||||
|
||||
- **Optional** will mark sumission plugin optional
|
||||
- **Active** will enable/disable plugin
|
||||
- **Tile Assembler Plugin** will set what should be used to assemble tiles on Deadline. Either **Open Image IO** will be used
|
||||
or Deadlines **Draft Tile Assembler**.
|
||||
- **Use Published scene** enable to render from published scene instead of scene in work area. Rendering from published files is much safer.
|
||||
- **Use Asset dependencies** will mark job pending on farm until asset dependencies are fulfilled - for example Deadline will wait for scene file to be synced to cloud, etc.
|
||||
- **Group name** use specific Deadline group for the job.
|
||||
- **Limit Groups** use these Deadline Limit groups for the job.
|
||||
- **Additional `JobInfo` data** JSON of additional Deadline options that will be embedded in `JobInfo` part of the submission data.
|
||||
- **Additional `PluginInfo` data** JSON of additional Deadline options that will be embedded in `PluginInfo` part of the submission data.
|
||||
- **Scene patches** - configure mechanism to add additional lines to published Maya Ascii scene files before they are used for rendering.
|
||||
This is useful to fix some specific renderer glitches and advanced hacking of Maya Scene files. `Patch name` is label for patch for easier orientation.
|
||||
`Patch regex` is regex used to find line in file, after `Patch line` string is inserted. Note that you need to add line ending.
|
||||
|
||||
## Custom Menu
|
||||
You can add your custom tools menu into Maya by extending definitions in **Maya -> Scripts Menu Definition**.
|
||||

|
||||
|
|
@ -94,4 +114,9 @@ You can add your custom tools menu into Maya by extending definitions in **Maya
|
|||
:::note Work in progress
|
||||
This is still work in progress. Menu definition will be handled more friendly with widgets and not
|
||||
raw json.
|
||||
:::
|
||||
:::
|
||||
|
||||
## Multiplatform path mapping
|
||||
You can configure path mapping using Maya `dirmap` command. This will add bi-directional mapping between
|
||||
list of paths specified in **Settings**. You can find it in **Settings -> Project Settings -> Maya -> Maya Directory Mapping**
|
||||

|
||||
|
|
|
|||
BIN
website/docs/assets/maya-admin_dirmap_settings.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
website/docs/assets/maya-admin_submit_maya_job_to_deadline.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 582 151" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,-654,-2331)">
|
||||
<g id="pypeclub_black" transform="matrix(0.775972,0,0,0.911492,654.799,1794.12)">
|
||||
<g transform="matrix(1,0,0,1,-987,-2015)">
|
||||
<g id="pypeclub_black" transform="matrix(0.775972,0,0,0.911492,987.586,1477.95)">
|
||||
<rect x="0" y="589.923" width="748.802" height="164.565" style="fill:none;"/>
|
||||
<g transform="matrix(2.7183,0,0,1.24969,-3.76111,624.796)">
|
||||
<g id="PYPE" transform="matrix(1,0,0,1,-105.172,0)">
|
||||
|
|
@ -20,7 +20,21 @@
|
|||
</g>
|
||||
</g>
|
||||
<g transform="matrix(0.474085,0,0,0.877899,142.749,-0.167247)">
|
||||
<text x="2.312px" y="79.899px" style="font-family:'Poppins-Light', 'Poppins';font-weight:300;font-size:100px;">.club</text>
|
||||
<g transform="matrix(100,0,0,100,2.312,79.8987)">
|
||||
<path d="M0.092,0.005C0.077,0.005 0.065,-0 0.056,-0.01C0.046,-0.02 0.041,-0.032 0.041,-0.047C0.041,-0.062 0.046,-0.074 0.056,-0.084C0.065,-0.093 0.077,-0.098 0.092,-0.098C0.106,-0.098 0.118,-0.093 0.128,-0.084C0.137,-0.074 0.142,-0.062 0.142,-0.047C0.142,-0.032 0.137,-0.02 0.128,-0.01C0.118,-0 0.106,0.005 0.092,0.005Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(100,0,0,100,20.5107,79.8987)">
|
||||
<path d="M0.048,-0.273C0.048,-0.33 0.059,-0.379 0.082,-0.422C0.105,-0.464 0.136,-0.497 0.176,-0.52C0.216,-0.543 0.262,-0.554 0.313,-0.554C0.38,-0.554 0.436,-0.537 0.48,-0.504C0.523,-0.471 0.551,-0.425 0.564,-0.368L0.489,-0.368C0.48,-0.407 0.46,-0.438 0.429,-0.461C0.398,-0.483 0.359,-0.494 0.313,-0.494C0.276,-0.494 0.243,-0.486 0.214,-0.469C0.185,-0.452 0.162,-0.428 0.145,-0.395C0.128,-0.362 0.119,-0.321 0.119,-0.273C0.119,-0.225 0.128,-0.184 0.145,-0.151C0.162,-0.118 0.185,-0.093 0.214,-0.076C0.243,-0.059 0.276,-0.051 0.313,-0.051C0.359,-0.051 0.398,-0.062 0.429,-0.085C0.46,-0.107 0.48,-0.138 0.489,-0.178L0.564,-0.178C0.551,-0.122 0.523,-0.077 0.479,-0.043C0.435,-0.009 0.38,0.008 0.313,0.008C0.262,0.008 0.216,-0.004 0.176,-0.027C0.136,-0.05 0.105,-0.082 0.082,-0.125C0.059,-0.167 0.048,-0.216 0.048,-0.273Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(100,0,0,100,81.8095,79.8987)">
|
||||
<rect x="0.08" y="-0.74" width="0.07" height="0.74" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(100,0,0,100,104.708,79.8987)">
|
||||
<path d="M0.553,-0.546L0.553,-0L0.483,-0L0.483,-0.096C0.467,-0.062 0.442,-0.036 0.409,-0.018C0.376,-0 0.338,0.009 0.297,0.009C0.232,0.009 0.178,-0.011 0.137,-0.051C0.096,-0.092 0.075,-0.15 0.075,-0.227L0.075,-0.546L0.144,-0.546L0.144,-0.235C0.144,-0.176 0.159,-0.13 0.189,-0.099C0.218,-0.068 0.259,-0.052 0.31,-0.052C0.363,-0.052 0.405,-0.069 0.436,-0.102C0.467,-0.135 0.483,-0.184 0.483,-0.249L0.483,-0.546L0.553,-0.546Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(100,0,0,100,168.007,79.8987)">
|
||||
<path d="M0.149,-0.425C0.167,-0.463 0.195,-0.494 0.233,-0.518C0.27,-0.542 0.315,-0.554 0.366,-0.554C0.416,-0.554 0.461,-0.543 0.5,-0.52C0.539,-0.497 0.57,-0.464 0.593,-0.422C0.615,-0.379 0.626,-0.33 0.626,-0.274C0.626,-0.218 0.615,-0.169 0.593,-0.126C0.57,-0.083 0.539,-0.05 0.5,-0.027C0.46,-0.004 0.415,0.008 0.366,0.008C0.314,0.008 0.269,-0.004 0.232,-0.028C0.194,-0.051 0.166,-0.082 0.149,-0.12L0.149,-0L0.08,-0L0.08,-0.74L0.149,-0.74L0.149,-0.425ZM0.555,-0.274C0.555,-0.319 0.546,-0.359 0.529,-0.392C0.511,-0.425 0.487,-0.45 0.456,-0.467C0.425,-0.484 0.391,-0.493 0.352,-0.493C0.315,-0.493 0.281,-0.484 0.25,-0.466C0.219,-0.448 0.194,-0.422 0.176,-0.389C0.158,-0.356 0.149,-0.317 0.149,-0.273C0.149,-0.229 0.158,-0.19 0.176,-0.157C0.194,-0.124 0.219,-0.098 0.25,-0.08C0.281,-0.062 0.315,-0.053 0.352,-0.053C0.391,-0.053 0.425,-0.062 0.456,-0.08C0.487,-0.097 0.511,-0.123 0.529,-0.157C0.546,-0.19 0.555,-0.229 0.555,-0.274Z" style="fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 6.9 KiB |
|
|
@ -1,26 +1,40 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 582 151" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,-35,-2135)">
|
||||
<g id="pypeclub_color_white" transform="matrix(0.775972,0,0,0.911492,35.6167,1597.77)">
|
||||
<g transform="matrix(1,0,0,1,-987,-1797)">
|
||||
<g id="pypeclub_color_white" transform="matrix(0.775972,0,0,0.911492,987.586,1259.42)">
|
||||
<rect x="0" y="589.923" width="748.802" height="164.565" style="fill:none;"/>
|
||||
<g transform="matrix(2.7183,0,0,1.24969,-3.76111,624.796)">
|
||||
<g id="PYPE" transform="matrix(1,0,0,1,-105.172,0)">
|
||||
<g transform="matrix(47.4085,0,0,87.7899,124.81,70.8632)">
|
||||
<path d="M0.629,-0.465C0.629,-0.42 0.619,-0.38 0.598,-0.344C0.577,-0.307 0.547,-0.278 0.506,-0.257C0.466,-0.236 0.417,-0.225 0.36,-0.225L0.272,-0.225L0.272,-0.05C0.272,-0.037 0.267,-0.024 0.257,-0.015C0.248,-0.005 0.235,0 0.222,0L0.1,0C0.072,0 0.05,-0.022 0.05,-0.05L0.05,-0.658C0.05,-0.686 0.072,-0.708 0.1,-0.708L0.36,-0.708C0.447,-0.708 0.513,-0.686 0.56,-0.642C0.606,-0.598 0.629,-0.539 0.629,-0.465ZM0.335,-0.4C0.381,-0.4 0.404,-0.422 0.404,-0.465C0.404,-0.508 0.381,-0.53 0.335,-0.53L0.272,-0.53L0.272,-0.4L0.335,-0.4Z" style="fill:url(#_Linear1);fill-rule:nonzero;"/>
|
||||
<path d="M0.629,-0.465C0.629,-0.42 0.619,-0.38 0.598,-0.344C0.577,-0.307 0.547,-0.278 0.506,-0.257C0.466,-0.236 0.417,-0.225 0.36,-0.225L0.272,-0.225L0.272,-0.05C0.272,-0.037 0.267,-0.024 0.257,-0.015C0.248,-0.005 0.235,0 0.222,0L0.1,-0C0.072,0 0.05,-0.022 0.05,-0.05L0.05,-0.658C0.05,-0.686 0.072,-0.708 0.1,-0.708L0.36,-0.708C0.447,-0.708 0.513,-0.686 0.56,-0.642C0.606,-0.598 0.629,-0.539 0.629,-0.465ZM0.335,-0.4C0.381,-0.4 0.404,-0.422 0.404,-0.465C0.404,-0.508 0.381,-0.53 0.335,-0.53L0.272,-0.53L0.272,-0.4L0.335,-0.4Z" style="fill:url(#_Linear1);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(47.4085,0,0,87.7899,155.577,70.8632)">
|
||||
<path d="M0.648,-0.708C0.665,-0.708 0.682,-0.699 0.691,-0.684C0.7,-0.669 0.7,-0.651 0.692,-0.635C0.622,-0.498 0.476,-0.215 0.476,-0.215L0.476,-0.05C0.476,-0.022 0.454,0 0.426,0L0.304,0C0.276,0 0.254,-0.022 0.254,-0.05L0.254,-0.215C0.254,-0.215 0.108,-0.498 0.038,-0.635C0.03,-0.651 0.03,-0.669 0.039,-0.684C0.048,-0.699 0.065,-0.708 0.082,-0.708L0.222,-0.708C0.241,-0.708 0.259,-0.696 0.267,-0.679C0.297,-0.612 0.367,-0.457 0.367,-0.457C0.367,-0.457 0.437,-0.612 0.467,-0.679C0.475,-0.696 0.493,-0.708 0.512,-0.708L0.648,-0.708Z" style="fill:url(#_Linear2);fill-rule:nonzero;"/>
|
||||
<path d="M0.648,-0.708C0.665,-0.708 0.682,-0.699 0.691,-0.684C0.7,-0.669 0.7,-0.651 0.692,-0.635C0.622,-0.498 0.476,-0.215 0.476,-0.215L0.476,-0.05C0.476,-0.022 0.454,0 0.426,0L0.304,-0C0.276,0 0.254,-0.022 0.254,-0.05L0.254,-0.215C0.254,-0.215 0.108,-0.498 0.038,-0.635C0.03,-0.651 0.03,-0.669 0.039,-0.684C0.048,-0.699 0.065,-0.708 0.082,-0.708L0.222,-0.708C0.241,-0.708 0.259,-0.696 0.267,-0.679C0.297,-0.612 0.367,-0.457 0.367,-0.457C0.367,-0.457 0.437,-0.612 0.467,-0.679C0.475,-0.696 0.493,-0.708 0.512,-0.708L0.648,-0.708Z" style="fill:url(#_Linear2);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(47.4085,0,0,87.7899,190.185,70.8632)">
|
||||
<path d="M0.629,-0.465C0.629,-0.42 0.619,-0.38 0.598,-0.344C0.577,-0.307 0.547,-0.278 0.506,-0.257C0.466,-0.236 0.417,-0.225 0.36,-0.225L0.272,-0.225L0.272,-0.05C0.272,-0.037 0.267,-0.024 0.257,-0.015C0.248,-0.005 0.235,0 0.222,0L0.1,0C0.072,0 0.05,-0.022 0.05,-0.05L0.05,-0.658C0.05,-0.686 0.072,-0.708 0.1,-0.708L0.36,-0.708C0.447,-0.708 0.513,-0.686 0.56,-0.642C0.606,-0.598 0.629,-0.539 0.629,-0.465ZM0.335,-0.4C0.381,-0.4 0.404,-0.422 0.404,-0.465C0.404,-0.508 0.381,-0.53 0.335,-0.53L0.272,-0.53L0.272,-0.4L0.335,-0.4Z" style="fill:url(#_Linear3);fill-rule:nonzero;"/>
|
||||
<path d="M0.629,-0.465C0.629,-0.42 0.619,-0.38 0.598,-0.344C0.577,-0.307 0.547,-0.278 0.506,-0.257C0.466,-0.236 0.417,-0.225 0.36,-0.225L0.272,-0.225L0.272,-0.05C0.272,-0.037 0.267,-0.024 0.257,-0.015C0.248,-0.005 0.235,0 0.222,0L0.1,-0C0.072,0 0.05,-0.022 0.05,-0.05L0.05,-0.658C0.05,-0.686 0.072,-0.708 0.1,-0.708L0.36,-0.708C0.447,-0.708 0.513,-0.686 0.56,-0.642C0.606,-0.598 0.629,-0.539 0.629,-0.465ZM0.335,-0.4C0.381,-0.4 0.404,-0.422 0.404,-0.465C0.404,-0.508 0.381,-0.53 0.335,-0.53L0.272,-0.53L0.272,-0.4L0.335,-0.4Z" style="fill:url(#_Linear3);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(47.4085,0,0,87.7899,220.953,70.8632)">
|
||||
<path d="M0.272,-0.531L0.272,-0.444L0.492,-0.444L0.492,-0.277L0.272,-0.277L0.272,-0.177L0.472,-0.177C0.485,-0.177 0.498,-0.172 0.507,-0.162C0.517,-0.153 0.522,-0.14 0.522,-0.127L0.522,-0.05C0.522,-0.037 0.517,-0.024 0.507,-0.015C0.498,-0.005 0.485,0 0.472,-0L0.1,0C0.072,0 0.05,-0.022 0.05,-0.05L0.05,-0.658C0.05,-0.686 0.072,-0.708 0.1,-0.708L0.472,-0.708C0.485,-0.708 0.498,-0.703 0.507,-0.693C0.517,-0.684 0.522,-0.671 0.522,-0.658L0.522,-0.581C0.522,-0.568 0.517,-0.555 0.507,-0.546C0.498,-0.536 0.485,-0.531 0.472,-0.531L0.272,-0.531Z" style="fill:url(#_Linear4);fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="matrix(0.474085,0,0,0.877899,142.749,-0.167247)">
|
||||
<text x="2.312px" y="79.899px" style="font-family:'Poppins-Light', 'Poppins';font-weight:300;font-size:100px;fill:white;">.club</text>
|
||||
<g transform="matrix(100,0,0,100,2.312,79.8987)">
|
||||
<path d="M0.092,0.005C0.077,0.005 0.065,-0 0.056,-0.01C0.046,-0.02 0.041,-0.032 0.041,-0.047C0.041,-0.062 0.046,-0.074 0.056,-0.084C0.065,-0.093 0.077,-0.098 0.092,-0.098C0.106,-0.098 0.118,-0.093 0.128,-0.084C0.137,-0.074 0.142,-0.062 0.142,-0.047C0.142,-0.032 0.137,-0.02 0.128,-0.01C0.118,-0 0.106,0.005 0.092,0.005Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(100,0,0,100,20.5107,79.8987)">
|
||||
<path d="M0.048,-0.273C0.048,-0.33 0.059,-0.379 0.082,-0.422C0.105,-0.464 0.136,-0.497 0.176,-0.52C0.216,-0.543 0.262,-0.554 0.313,-0.554C0.38,-0.554 0.436,-0.537 0.48,-0.504C0.523,-0.471 0.551,-0.425 0.564,-0.368L0.489,-0.368C0.48,-0.407 0.46,-0.438 0.429,-0.461C0.398,-0.483 0.359,-0.494 0.313,-0.494C0.276,-0.494 0.243,-0.486 0.214,-0.469C0.185,-0.452 0.162,-0.428 0.145,-0.395C0.128,-0.362 0.119,-0.321 0.119,-0.273C0.119,-0.225 0.128,-0.184 0.145,-0.151C0.162,-0.118 0.185,-0.093 0.214,-0.076C0.243,-0.059 0.276,-0.051 0.313,-0.051C0.359,-0.051 0.398,-0.062 0.429,-0.085C0.46,-0.107 0.48,-0.138 0.489,-0.178L0.564,-0.178C0.551,-0.122 0.523,-0.077 0.479,-0.043C0.435,-0.009 0.38,0.008 0.313,0.008C0.262,0.008 0.216,-0.004 0.176,-0.027C0.136,-0.05 0.105,-0.082 0.082,-0.125C0.059,-0.167 0.048,-0.216 0.048,-0.273Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(100,0,0,100,81.8095,79.8987)">
|
||||
<rect x="0.08" y="-0.74" width="0.07" height="0.74" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(100,0,0,100,104.708,79.8987)">
|
||||
<path d="M0.553,-0.546L0.553,-0L0.483,-0L0.483,-0.096C0.467,-0.062 0.442,-0.036 0.409,-0.018C0.376,-0 0.338,0.009 0.297,0.009C0.232,0.009 0.178,-0.011 0.137,-0.051C0.096,-0.092 0.075,-0.15 0.075,-0.227L0.075,-0.546L0.144,-0.546L0.144,-0.235C0.144,-0.176 0.159,-0.13 0.189,-0.099C0.218,-0.068 0.259,-0.052 0.31,-0.052C0.363,-0.052 0.405,-0.069 0.436,-0.102C0.467,-0.135 0.483,-0.184 0.483,-0.249L0.483,-0.546L0.553,-0.546Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(100,0,0,100,168.007,79.8987)">
|
||||
<path d="M0.149,-0.425C0.167,-0.463 0.195,-0.494 0.233,-0.518C0.27,-0.542 0.315,-0.554 0.366,-0.554C0.416,-0.554 0.461,-0.543 0.5,-0.52C0.539,-0.497 0.57,-0.464 0.593,-0.422C0.615,-0.379 0.626,-0.33 0.626,-0.274C0.626,-0.218 0.615,-0.169 0.593,-0.126C0.57,-0.083 0.539,-0.05 0.5,-0.027C0.46,-0.004 0.415,0.008 0.366,0.008C0.314,0.008 0.269,-0.004 0.232,-0.028C0.194,-0.051 0.166,-0.082 0.149,-0.12L0.149,-0L0.08,-0L0.08,-0.74L0.149,-0.74L0.149,-0.425ZM0.555,-0.274C0.555,-0.319 0.546,-0.359 0.529,-0.392C0.511,-0.425 0.487,-0.45 0.456,-0.467C0.425,-0.484 0.391,-0.493 0.352,-0.493C0.315,-0.493 0.281,-0.484 0.25,-0.466C0.219,-0.448 0.194,-0.422 0.176,-0.389C0.158,-0.356 0.149,-0.317 0.149,-0.273C0.149,-0.229 0.158,-0.19 0.176,-0.157C0.194,-0.124 0.219,-0.098 0.25,-0.08C0.281,-0.062 0.315,-0.053 0.352,-0.053C0.391,-0.053 0.425,-0.062 0.456,-0.08C0.487,-0.097 0.511,-0.123 0.529,-0.157C0.546,-0.19 0.555,-0.229 0.555,-0.274Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 8.6 KiB |
|
|
@ -1,8 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg width="100%" height="100%" viewBox="0 0 582 151" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
|
||||
<g transform="matrix(1,0,0,1,-654,-2135)">
|
||||
<g id="pypeclub_color_white" transform="matrix(0.775972,0,0,0.911492,654.799,1597.77)">
|
||||
<g transform="matrix(1,0,0,1,-987,-2234)">
|
||||
<g id="pypeclub_white" transform="matrix(0.775972,0,0,0.911492,987.586,1696.48)">
|
||||
<rect x="0" y="589.923" width="748.802" height="164.565" style="fill:none;"/>
|
||||
<g transform="matrix(2.7183,0,0,1.24969,-3.76111,624.796)">
|
||||
<g id="PYPE" transform="matrix(1,0,0,1,-105.172,0)">
|
||||
|
|
@ -20,7 +20,21 @@
|
|||
</g>
|
||||
</g>
|
||||
<g transform="matrix(0.474085,0,0,0.877899,142.749,-0.167247)">
|
||||
<text x="2.312px" y="79.899px" style="font-family:'Poppins-Light', 'Poppins';font-weight:300;font-size:100px;fill:white;">.club</text>
|
||||
<g transform="matrix(100,0,0,100,2.312,79.8987)">
|
||||
<path d="M0.092,0.005C0.077,0.005 0.065,-0 0.056,-0.01C0.046,-0.02 0.041,-0.032 0.041,-0.047C0.041,-0.062 0.046,-0.074 0.056,-0.084C0.065,-0.093 0.077,-0.098 0.092,-0.098C0.106,-0.098 0.118,-0.093 0.128,-0.084C0.137,-0.074 0.142,-0.062 0.142,-0.047C0.142,-0.032 0.137,-0.02 0.128,-0.01C0.118,-0 0.106,0.005 0.092,0.005Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(100,0,0,100,20.5107,79.8987)">
|
||||
<path d="M0.048,-0.273C0.048,-0.33 0.059,-0.379 0.082,-0.422C0.105,-0.464 0.136,-0.497 0.176,-0.52C0.216,-0.543 0.262,-0.554 0.313,-0.554C0.38,-0.554 0.436,-0.537 0.48,-0.504C0.523,-0.471 0.551,-0.425 0.564,-0.368L0.489,-0.368C0.48,-0.407 0.46,-0.438 0.429,-0.461C0.398,-0.483 0.359,-0.494 0.313,-0.494C0.276,-0.494 0.243,-0.486 0.214,-0.469C0.185,-0.452 0.162,-0.428 0.145,-0.395C0.128,-0.362 0.119,-0.321 0.119,-0.273C0.119,-0.225 0.128,-0.184 0.145,-0.151C0.162,-0.118 0.185,-0.093 0.214,-0.076C0.243,-0.059 0.276,-0.051 0.313,-0.051C0.359,-0.051 0.398,-0.062 0.429,-0.085C0.46,-0.107 0.48,-0.138 0.489,-0.178L0.564,-0.178C0.551,-0.122 0.523,-0.077 0.479,-0.043C0.435,-0.009 0.38,0.008 0.313,0.008C0.262,0.008 0.216,-0.004 0.176,-0.027C0.136,-0.05 0.105,-0.082 0.082,-0.125C0.059,-0.167 0.048,-0.216 0.048,-0.273Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(100,0,0,100,81.8095,79.8987)">
|
||||
<rect x="0.08" y="-0.74" width="0.07" height="0.74" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(100,0,0,100,104.708,79.8987)">
|
||||
<path d="M0.553,-0.546L0.553,-0L0.483,-0L0.483,-0.096C0.467,-0.062 0.442,-0.036 0.409,-0.018C0.376,-0 0.338,0.009 0.297,0.009C0.232,0.009 0.178,-0.011 0.137,-0.051C0.096,-0.092 0.075,-0.15 0.075,-0.227L0.075,-0.546L0.144,-0.546L0.144,-0.235C0.144,-0.176 0.159,-0.13 0.189,-0.099C0.218,-0.068 0.259,-0.052 0.31,-0.052C0.363,-0.052 0.405,-0.069 0.436,-0.102C0.467,-0.135 0.483,-0.184 0.483,-0.249L0.483,-0.546L0.553,-0.546Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
<g transform="matrix(100,0,0,100,168.007,79.8987)">
|
||||
<path d="M0.149,-0.425C0.167,-0.463 0.195,-0.494 0.233,-0.518C0.27,-0.542 0.315,-0.554 0.366,-0.554C0.416,-0.554 0.461,-0.543 0.5,-0.52C0.539,-0.497 0.57,-0.464 0.593,-0.422C0.615,-0.379 0.626,-0.33 0.626,-0.274C0.626,-0.218 0.615,-0.169 0.593,-0.126C0.57,-0.083 0.539,-0.05 0.5,-0.027C0.46,-0.004 0.415,0.008 0.366,0.008C0.314,0.008 0.269,-0.004 0.232,-0.028C0.194,-0.051 0.166,-0.082 0.149,-0.12L0.149,-0L0.08,-0L0.08,-0.74L0.149,-0.74L0.149,-0.425ZM0.555,-0.274C0.555,-0.319 0.546,-0.359 0.529,-0.392C0.511,-0.425 0.487,-0.45 0.456,-0.467C0.425,-0.484 0.391,-0.493 0.352,-0.493C0.315,-0.493 0.281,-0.484 0.25,-0.466C0.219,-0.448 0.194,-0.422 0.176,-0.389C0.158,-0.356 0.149,-0.317 0.149,-0.273C0.149,-0.229 0.158,-0.19 0.176,-0.157C0.194,-0.124 0.219,-0.098 0.25,-0.08C0.281,-0.062 0.315,-0.053 0.352,-0.053C0.391,-0.053 0.425,-0.062 0.456,-0.08C0.487,-0.097 0.511,-0.123 0.529,-0.157C0.546,-0.19 0.555,-0.229 0.555,-0.274Z" style="fill:white;fill-rule:nonzero;"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 7 KiB |