diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000000..b515851c81
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,7 @@
+[submodule "vendor/powershell/BurntToast"]
+ path = vendor/powershell/BurntToast
+ url = https://github.com/Windos/BurntToast.git
+
+[submodule "vendor/powershell/PSWriteColor"]
+ path = vendor/powershell/PSWriteColor
+ url = https://github.com/EvotecIT/PSWriteColor.git
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a0c058f73..cc5bf39a29 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,8 +1,8 @@
# Changelog
-## [3.12.1-nightly.4](https://github.com/pypeclub/OpenPype/tree/HEAD)
+## [3.12.1](https://github.com/pypeclub/OpenPype/tree/3.12.1) (2022-07-13)
-[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.0...HEAD)
+[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.12.0...3.12.1)
### 📖 Documentation
@@ -14,6 +14,9 @@
**🚀 Enhancements**
+- TrayPublisher: Added more options for grouping of instances [\#3494](https://github.com/pypeclub/OpenPype/pull/3494)
+- NewPublisher: Align creator attributes from top to bottom [\#3487](https://github.com/pypeclub/OpenPype/pull/3487)
+- NewPublisher: Added ability to use label of instance [\#3484](https://github.com/pypeclub/OpenPype/pull/3484)
- General: Creator Plugins have access to project [\#3476](https://github.com/pypeclub/OpenPype/pull/3476)
- General: Better arguments order in creator init [\#3475](https://github.com/pypeclub/OpenPype/pull/3475)
- Ftrack: Trigger custom ftrack events on project creation and preparation [\#3465](https://github.com/pypeclub/OpenPype/pull/3465)
@@ -21,10 +24,15 @@
- Blender: Bugfix - Set fps properly on open [\#3426](https://github.com/pypeclub/OpenPype/pull/3426)
- Hiero: Add custom scripts menu [\#3425](https://github.com/pypeclub/OpenPype/pull/3425)
- Blender: pre pyside install for all platforms [\#3400](https://github.com/pypeclub/OpenPype/pull/3400)
-- Maya: Ability to set resolution for playblasts from asset, and override through review instance. [\#3360](https://github.com/pypeclub/OpenPype/pull/3360)
+- Maya: Add additional playblast options to review Extractor. [\#3384](https://github.com/pypeclub/OpenPype/pull/3384)
**🐛 Bug fixes**
+- TrayPublisher: Keep use instance label in list view [\#3493](https://github.com/pypeclub/OpenPype/pull/3493)
+- General: Extract review use first frame of input sequence [\#3491](https://github.com/pypeclub/OpenPype/pull/3491)
+- General: Fix Plist loading for application launch [\#3485](https://github.com/pypeclub/OpenPype/pull/3485)
+- Nuke: Workfile tools open on start [\#3479](https://github.com/pypeclub/OpenPype/pull/3479)
+- New Publisher: Disabled context change allows creation [\#3478](https://github.com/pypeclub/OpenPype/pull/3478)
- General: thumbnail extractor fix [\#3474](https://github.com/pypeclub/OpenPype/pull/3474)
- Kitsu: bugfix with sync-service ans publish plugins [\#3473](https://github.com/pypeclub/OpenPype/pull/3473)
- Flame: solved problem with multi-selected loading [\#3470](https://github.com/pypeclub/OpenPype/pull/3470)
@@ -38,6 +46,7 @@
- Nuke: Slate frame is integrated [\#3427](https://github.com/pypeclub/OpenPype/pull/3427)
- Maya: Camera extra data - additional fix for \#3304 [\#3386](https://github.com/pypeclub/OpenPype/pull/3386)
- Maya: Handle excluding `model` family from frame range validator. [\#3370](https://github.com/pypeclub/OpenPype/pull/3370)
+- Harmony: audio validator has wrong logic [\#3364](https://github.com/pypeclub/OpenPype/pull/3364)
**🔀 Refactored code**
@@ -46,7 +55,9 @@
- General: Use query functions in global plugins [\#3459](https://github.com/pypeclub/OpenPype/pull/3459)
- Clockify: Use query functions in clockify actions [\#3458](https://github.com/pypeclub/OpenPype/pull/3458)
- General: Use query functions in rest api calls [\#3457](https://github.com/pypeclub/OpenPype/pull/3457)
+- General: Use query functions in openpype lib functions [\#3454](https://github.com/pypeclub/OpenPype/pull/3454)
- General: Use query functions in load utils [\#3446](https://github.com/pypeclub/OpenPype/pull/3446)
+- General: Move publish plugin and publish render abstractions [\#3442](https://github.com/pypeclub/OpenPype/pull/3442)
- General: Use Anatomy after move to pipeline [\#3436](https://github.com/pypeclub/OpenPype/pull/3436)
- General: Anatomy moved to pipeline [\#3435](https://github.com/pypeclub/OpenPype/pull/3435)
- Fusion: Use client query functions [\#3380](https://github.com/pypeclub/OpenPype/pull/3380)
@@ -66,8 +77,6 @@
- Webserver: Added CORS middleware [\#3422](https://github.com/pypeclub/OpenPype/pull/3422)
- Attribute Defs UI: Files widget show what is allowed to drop in [\#3411](https://github.com/pypeclub/OpenPype/pull/3411)
- General: Add ability to change user value for templates [\#3366](https://github.com/pypeclub/OpenPype/pull/3366)
-- Hosts: More options for in-host callbacks [\#3357](https://github.com/pypeclub/OpenPype/pull/3357)
-- Multiverse: expose some settings to GUI [\#3350](https://github.com/pypeclub/OpenPype/pull/3350)
**🐛 Bug fixes**
@@ -82,7 +91,6 @@
- Maya: vray device aspect ratio fix [\#3381](https://github.com/pypeclub/OpenPype/pull/3381)
- Flame: bunch of publishing issues [\#3377](https://github.com/pypeclub/OpenPype/pull/3377)
- Harmony: added unc path to zifile command in Harmony [\#3372](https://github.com/pypeclub/OpenPype/pull/3372)
-- Standalone: settings improvements [\#3355](https://github.com/pypeclub/OpenPype/pull/3355)
**🔀 Refactored code**
@@ -107,37 +115,20 @@
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.1-nightly.1...3.11.1)
-**🆕 New features**
-
-- Flame: custom export temp folder [\#3346](https://github.com/pypeclub/OpenPype/pull/3346)
-- Nuke: removing third-party plugins [\#3344](https://github.com/pypeclub/OpenPype/pull/3344)
-
**🚀 Enhancements**
- Pyblish Pype: Hiding/Close issues [\#3367](https://github.com/pypeclub/OpenPype/pull/3367)
-- Ftrack: Removed requirement of pypeclub role from default settings [\#3354](https://github.com/pypeclub/OpenPype/pull/3354)
-- Kitsu: Prevent crash on missing frames information [\#3352](https://github.com/pypeclub/OpenPype/pull/3352)
**🐛 Bug fixes**
- Nuke: bake streams with slate on farm [\#3368](https://github.com/pypeclub/OpenPype/pull/3368)
-- Harmony: audio validator has wrong logic [\#3364](https://github.com/pypeclub/OpenPype/pull/3364)
- Nuke: Fix missing variable in extract thumbnail [\#3363](https://github.com/pypeclub/OpenPype/pull/3363)
- Nuke: Fix precollect writes [\#3361](https://github.com/pypeclub/OpenPype/pull/3361)
-- AE- fix validate\_scene\_settings and renderLocal [\#3358](https://github.com/pypeclub/OpenPype/pull/3358)
-- deadline: fixing misidentification of revieables [\#3356](https://github.com/pypeclub/OpenPype/pull/3356)
-- General: Create only one thumbnail per instance [\#3351](https://github.com/pypeclub/OpenPype/pull/3351)
-- nuke: adding extract thumbnail settings 3.10 [\#3347](https://github.com/pypeclub/OpenPype/pull/3347)
-- General: Fix last version function [\#3345](https://github.com/pypeclub/OpenPype/pull/3345)
## [3.11.0](https://github.com/pypeclub/OpenPype/tree/3.11.0) (2022-06-17)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.11.0-nightly.4...3.11.0)
-**🐛 Bug fixes**
-
-- General: Handle empty source key on instance [\#3342](https://github.com/pypeclub/OpenPype/pull/3342)
-
## [3.10.0](https://github.com/pypeclub/OpenPype/tree/3.10.0) (2022-05-26)
[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.10.0-nightly.6...3.10.0)
diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py
index 34340a13a5..e4221978c0 100644
--- a/openpype/hosts/maya/api/lib.py
+++ b/openpype/hosts/maya/api/lib.py
@@ -2522,12 +2522,30 @@ def load_capture_preset(data=None):
temp_options2['multiSampleEnable'] = False
temp_options2['multiSampleCount'] = preset[id][key]
+ if key == 'renderDepthOfField':
+ temp_options2['renderDepthOfField'] = preset[id][key]
+
if key == 'ssaoEnable':
if preset[id][key] is True:
temp_options2['ssaoEnable'] = True
else:
temp_options2['ssaoEnable'] = False
+ if key == 'ssaoSamples':
+ temp_options2['ssaoSamples'] = preset[id][key]
+
+ if key == 'ssaoAmount':
+ temp_options2['ssaoAmount'] = preset[id][key]
+
+ if key == 'ssaoRadius':
+ temp_options2['ssaoRadius'] = preset[id][key]
+
+ if key == 'hwFogDensity':
+ temp_options2['hwFogDensity'] = preset[id][key]
+
+ if key == 'ssaoFilterRadius':
+ temp_options2['ssaoFilterRadius'] = preset[id][key]
+
if key == 'alphaCut':
temp_options2['transparencyAlgorithm'] = 5
temp_options2['transparencyQuality'] = 1
@@ -2535,6 +2553,48 @@ def load_capture_preset(data=None):
if key == 'headsUpDisplay':
temp_options['headsUpDisplay'] = True
+ if key == 'fogging':
+ temp_options['fogging'] = preset[id][key] or False
+
+ if key == 'hwFogStart':
+ temp_options2['hwFogStart'] = preset[id][key]
+
+ if key == 'hwFogEnd':
+ temp_options2['hwFogEnd'] = preset[id][key]
+
+ if key == 'hwFogAlpha':
+ temp_options2['hwFogAlpha'] = preset[id][key]
+
+ if key == 'hwFogFalloff':
+ temp_options2['hwFogFalloff'] = int(preset[id][key])
+
+ if key == 'hwFogColorR':
+ temp_options2['hwFogColorR'] = preset[id][key]
+
+ if key == 'hwFogColorG':
+ temp_options2['hwFogColorG'] = preset[id][key]
+
+ if key == 'hwFogColorB':
+ temp_options2['hwFogColorB'] = preset[id][key]
+
+ if key == 'motionBlurEnable':
+ if preset[id][key] is True:
+ temp_options2['motionBlurEnable'] = True
+ else:
+ temp_options2['motionBlurEnable'] = False
+
+ if key == 'motionBlurSampleCount':
+ temp_options2['motionBlurSampleCount'] = preset[id][key]
+
+ if key == 'motionBlurShutterOpenFraction':
+ temp_options2['motionBlurShutterOpenFraction'] = preset[id][key]
+
+ if key == 'lineAAEnable':
+ if preset[id][key] is True:
+ temp_options2['lineAAEnable'] = True
+ else:
+ temp_options2['lineAAEnable'] = False
+
else:
temp_options[str(key)] = preset[id][key]
@@ -2544,7 +2604,24 @@ def load_capture_preset(data=None):
'gpuCacheDisplayFilter',
'multiSample',
'ssaoEnable',
- 'textureMaxResolution'
+ 'ssaoSamples',
+ 'ssaoAmount',
+ 'ssaoFilterRadius',
+ 'ssaoRadius',
+ 'hwFogStart',
+ 'hwFogEnd',
+ 'hwFogAlpha',
+ 'hwFogFalloff',
+ 'hwFogColorR',
+ 'hwFogColorG',
+ 'hwFogColorB',
+ 'hwFogDensity',
+ 'textureMaxResolution',
+ 'motionBlurEnable',
+ 'motionBlurSampleCount',
+ 'motionBlurShutterOpenFraction',
+ 'lineAAEnable',
+ 'renderDepthOfField'
]:
temp_options.pop(key, None)
diff --git a/openpype/hosts/nuke/plugins/load/load_clip.py b/openpype/hosts/nuke/plugins/load/load_clip.py
index d177e6ba76..b2dc4a52d7 100644
--- a/openpype/hosts/nuke/plugins/load/load_clip.py
+++ b/openpype/hosts/nuke/plugins/load/load_clip.py
@@ -54,20 +54,28 @@ class LoadClip(plugin.NukeLoader):
script_start = int(nuke.root()["first_frame"].value())
# option gui
- defaults = {
- "start_at_workfile": True
+ options_defaults = {
+ "start_at_workfile": True,
+ "add_retime": True
}
- options = [
- qargparse.Boolean(
- "start_at_workfile",
- help="Load at workfile start frame",
- default=True
- )
- ]
-
node_name_template = "{class_name}_{ext}"
+ @classmethod
+ def get_options(cls, *args):
+ return [
+ qargparse.Boolean(
+ "start_at_workfile",
+ help="Load at workfile start frame",
+ default=cls.options_defaults["start_at_workfile"]
+ ),
+ qargparse.Boolean(
+ "add_retime",
+ help="Load with retime",
+ default=cls.options_defaults["add_retime"]
+ )
+ ]
+
@classmethod
def get_representations(cls):
return (
@@ -86,7 +94,10 @@ class LoadClip(plugin.NukeLoader):
file = self.fname.replace("\\", "/")
start_at_workfile = options.get(
- "start_at_workfile", self.defaults["start_at_workfile"])
+ "start_at_workfile", self.options_defaults["start_at_workfile"])
+
+ add_retime = options.get(
+ "add_retime", self.options_defaults["add_retime"])
version = context['version']
version_data = version.get("data", {})
@@ -151,7 +162,7 @@ class LoadClip(plugin.NukeLoader):
data_imprint = {}
for k in add_keys:
if k == 'version':
- data_imprint.update({k: context["version"]['name']})
+ data_imprint[k] = context["version"]['name']
elif k == 'colorspace':
colorspace = repre["data"].get(k)
colorspace = colorspace or version_data.get(k)
@@ -159,10 +170,13 @@ class LoadClip(plugin.NukeLoader):
if used_colorspace:
data_imprint["used_colorspace"] = used_colorspace
else:
- data_imprint.update(
- {k: context["version"]['data'].get(k, str(None))})
+ data_imprint[k] = context["version"]['data'].get(
+ k, str(None))
- data_imprint.update({"objectName": read_name})
+ data_imprint["objectName"] = read_name
+
+ if add_retime and version_data.get("retime", None):
+ data_imprint["addRetime"] = True
read_node["tile_color"].setValue(int("0x4ecd25ff", 16))
@@ -174,7 +188,7 @@ class LoadClip(plugin.NukeLoader):
loader=self.__class__.__name__,
data=data_imprint)
- if version_data.get("retime", None):
+ if add_retime and version_data.get("retime", None):
self._make_retimes(read_node, version_data)
self.set_as_member(read_node)
@@ -198,7 +212,12 @@ class LoadClip(plugin.NukeLoader):
read_node = nuke.toNode(container['objectName'])
file = get_representation_path(representation).replace("\\", "/")
- start_at_workfile = bool("start at" in read_node['frame_mode'].value())
+ start_at_workfile = "start at" in read_node['frame_mode'].value()
+
+ add_retime = [
+ key for key in read_node.knobs().keys()
+ if "addRetime" in key
+ ]
project_name = legacy_io.active_project()
version_doc = get_version_by_id(project_name, representation["parent"])
@@ -286,7 +305,7 @@ class LoadClip(plugin.NukeLoader):
"updated to version: {}".format(version_doc.get("name"))
)
- if version_data.get("retime", None):
+ if add_retime and version_data.get("retime", None):
self._make_retimes(read_node, version_data)
else:
self.clear_members(read_node)
diff --git a/openpype/hosts/traypublisher/api/__init__.py b/openpype/hosts/traypublisher/api/__init__.py
index c461c0c526..4e7284b09a 100644
--- a/openpype/hosts/traypublisher/api/__init__.py
+++ b/openpype/hosts/traypublisher/api/__init__.py
@@ -1,20 +1,8 @@
from .pipeline import (
- install,
- ls,
-
- set_project_name,
- get_context_title,
- get_context_data,
- update_context_data,
+ TrayPublisherHost,
)
__all__ = (
- "install",
- "ls",
-
- "set_project_name",
- "get_context_title",
- "get_context_data",
- "update_context_data",
+ "TrayPublisherHost",
)
diff --git a/openpype/hosts/traypublisher/api/pipeline.py b/openpype/hosts/traypublisher/api/pipeline.py
index 954a0bae47..2d9db7801e 100644
--- a/openpype/hosts/traypublisher/api/pipeline.py
+++ b/openpype/hosts/traypublisher/api/pipeline.py
@@ -9,6 +9,8 @@ from openpype.pipeline import (
register_creator_plugin_path,
legacy_io,
)
+from openpype.host import HostBase, INewPublisher
+
ROOT_DIR = os.path.dirname(os.path.dirname(
os.path.abspath(__file__)
@@ -17,6 +19,35 @@ PUBLISH_PATH = os.path.join(ROOT_DIR, "plugins", "publish")
CREATE_PATH = os.path.join(ROOT_DIR, "plugins", "create")
+class TrayPublisherHost(HostBase, INewPublisher):
+ name = "traypublisher"
+
+ def install(self):
+ os.environ["AVALON_APP"] = self.name
+ legacy_io.Session["AVALON_APP"] = self.name
+
+ pyblish.api.register_host("traypublisher")
+ pyblish.api.register_plugin_path(PUBLISH_PATH)
+ register_creator_plugin_path(CREATE_PATH)
+
+ def get_context_title(self):
+ return HostContext.get_project_name()
+
+ def get_context_data(self):
+ return HostContext.get_context_data()
+
+ def update_context_data(self, data, changes):
+ HostContext.save_context_data(data, changes)
+
+ def set_project_name(self, project_name):
+ # TODO Deregister project specific plugins and register new project
+ # plugins
+ os.environ["AVALON_PROJECT"] = project_name
+ legacy_io.Session["AVALON_PROJECT"] = project_name
+ legacy_io.install()
+ HostContext.set_project_name(project_name)
+
+
class HostContext:
_context_json_path = None
@@ -150,32 +181,3 @@ def get_context_data():
def update_context_data(data, changes):
HostContext.save_context_data(data)
-
-
-def get_context_title():
- return HostContext.get_project_name()
-
-
-def ls():
- """Probably will never return loaded containers."""
- return []
-
-
-def install():
- """This is called before a project is known.
-
- Project is defined with 'set_project_name'.
- """
- os.environ["AVALON_APP"] = "traypublisher"
-
- pyblish.api.register_host("traypublisher")
- pyblish.api.register_plugin_path(PUBLISH_PATH)
- register_creator_plugin_path(CREATE_PATH)
-
-
-def set_project_name(project_name):
- # TODO Deregister project specific plugins and register new project plugins
- os.environ["AVALON_PROJECT"] = project_name
- legacy_io.Session["AVALON_PROJECT"] = project_name
- legacy_io.install()
- HostContext.set_project_name(project_name)
diff --git a/openpype/hosts/traypublisher/api/plugin.py b/openpype/hosts/traypublisher/api/plugin.py
index 202664cfc6..9b9425855e 100644
--- a/openpype/hosts/traypublisher/api/plugin.py
+++ b/openpype/hosts/traypublisher/api/plugin.py
@@ -1,8 +1,8 @@
+from openpype.lib.attribute_definitions import FileDef
from openpype.pipeline import (
Creator,
CreatedInstance
)
-from openpype.lib import FileDef
from .pipeline import (
list_instances,
@@ -12,6 +12,29 @@ from .pipeline import (
)
+IMAGE_EXTENSIONS = [
+ ".ani", ".anim", ".apng", ".art", ".bmp", ".bpg", ".bsave", ".cal",
+ ".cin", ".cpc", ".cpt", ".dds", ".dpx", ".ecw", ".exr", ".fits",
+ ".flic", ".flif", ".fpx", ".gif", ".hdri", ".hevc", ".icer",
+ ".icns", ".ico", ".cur", ".ics", ".ilbm", ".jbig", ".jbig2",
+ ".jng", ".jpeg", ".jpeg-ls", ".jpeg", ".2000", ".jpg", ".xr",
+ ".jpeg", ".xt", ".jpeg-hdr", ".kra", ".mng", ".miff", ".nrrd",
+ ".ora", ".pam", ".pbm", ".pgm", ".ppm", ".pnm", ".pcx", ".pgf",
+ ".pictor", ".png", ".psb", ".psp", ".qtvr", ".ras",
+ ".rgbe", ".logluv", ".tiff", ".sgi", ".tga", ".tiff", ".tiff/ep",
+ ".tiff/it", ".ufo", ".ufp", ".wbmp", ".webp", ".xbm", ".xcf",
+ ".xpm", ".xwd"
+]
+VIDEO_EXTENSIONS = [
+ ".3g2", ".3gp", ".amv", ".asf", ".avi", ".drc", ".f4a", ".f4b",
+ ".f4p", ".f4v", ".flv", ".gif", ".gifv", ".m2v", ".m4p", ".m4v",
+ ".mkv", ".mng", ".mov", ".mp2", ".mp4", ".mpe", ".mpeg", ".mpg",
+ ".mpv", ".mxf", ".nsv", ".ogg", ".ogv", ".qt", ".rm", ".rmvb",
+ ".roq", ".svi", ".vob", ".webm", ".wmv", ".yuv"
+]
+REVIEW_EXTENSIONS = IMAGE_EXTENSIONS + VIDEO_EXTENSIONS
+
+
class TrayPublishCreator(Creator):
create_allow_context_change = True
host_name = "traypublisher"
@@ -37,6 +60,21 @@ class TrayPublishCreator(Creator):
# Use same attributes as for instance attrobites
return self.get_instance_attr_defs()
+ def _store_new_instance(self, new_instance):
+ """Tray publisher specific method to store instance.
+
+ Instance is stored into "workfile" of traypublisher and also add it
+ to CreateContext.
+
+ Args:
+ new_instance (CreatedInstance): Instance that should be stored.
+ """
+
+ # Host implementation of storing metadata about instance
+ HostContext.add_instance(new_instance.data_to_store())
+ # Add instance to current context
+ self._add_instance_to_context(new_instance)
+
class SettingsCreator(TrayPublishCreator):
create_allow_context_change = True
@@ -58,19 +96,27 @@ class SettingsCreator(TrayPublishCreator):
data["settings_creator"] = True
# Create new instance
new_instance = CreatedInstance(self.family, subset_name, data, self)
- # Host implementation of storing metadata about instance
- HostContext.add_instance(new_instance.data_to_store())
- # Add instance to current context
- self._add_instance_to_context(new_instance)
+
+ self._store_new_instance(new_instance)
def get_instance_attr_defs(self):
return [
FileDef(
- "filepath",
+ "representation_files",
folders=False,
extensions=self.extensions,
allow_sequences=self.allow_sequences,
- label="Filepath",
+ single_item=not self.allow_multiple_items,
+ label="Representations",
+ ),
+ FileDef(
+ "reviewable",
+ folders=False,
+ extensions=REVIEW_EXTENSIONS,
+ allow_sequences=True,
+ single_item=True,
+ label="Reviewable representations",
+ extensions_label="Single reviewable item"
)
]
@@ -92,6 +138,7 @@ class SettingsCreator(TrayPublishCreator):
"detailed_description": item_data["detailed_description"],
"extensions": item_data["extensions"],
"allow_sequences": item_data["allow_sequences"],
+ "allow_multiple_items": item_data["allow_multiple_items"],
"default_variants": item_data["default_variants"]
}
)
diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_review_family.py b/openpype/hosts/traypublisher/plugins/publish/collect_review_family.py
deleted file mode 100644
index 965e251527..0000000000
--- a/openpype/hosts/traypublisher/plugins/publish/collect_review_family.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import pyblish.api
-from openpype.lib import BoolDef
-from openpype.pipeline import OpenPypePyblishPluginMixin
-
-
-class CollectReviewFamily(
- pyblish.api.InstancePlugin, OpenPypePyblishPluginMixin
-):
- """Add review family."""
-
- label = "Collect Review Family"
- order = pyblish.api.CollectorOrder - 0.49
-
- hosts = ["traypublisher"]
- families = [
- "image",
- "render",
- "plate",
- "review"
- ]
-
- def process(self, instance):
- values = self.get_attr_values_from_data(instance.data)
- if values.get("add_review_family"):
- instance.data["families"].append("review")
-
- @classmethod
- def get_attribute_defs(cls):
- return [
- BoolDef("add_review_family", label="Review", default=True)
- ]
diff --git a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py
index b2be43c701..c0ae694c3c 100644
--- a/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py
+++ b/openpype/hosts/traypublisher/plugins/publish/collect_simple_instances.py
@@ -1,9 +1,31 @@
import os
+import tempfile
+
+import clique
import pyblish.api
class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin):
- """Collect data for instances created by settings creators."""
+ """Collect data for instances created by settings creators.
+
+ Plugin create representations for simple instances based
+ on 'representation_files' attribute stored on instance data.
+
+ There is also possibility to have reviewable representation which can be
+ stored under 'reviewable' attribute stored on instance data. If there was
+ already created representation with the same files as 'revieable' containes
+
+ Representations can be marked for review and in that case is also added
+ 'review' family to instance families. For review can be marked only one
+ representation so **first** representation that has extension available
+ in '_review_extensions' is used for review.
+
+ For instance 'source' is used path from last representation created
+ from 'representation_files'.
+
+ Set staging directory on instance. That is probably never used because
+ each created representation has it's own staging dir.
+ """
label = "Collect Settings Simple Instances"
order = pyblish.api.CollectorOrder - 0.49
@@ -14,37 +36,193 @@ class CollectSettingsSimpleInstances(pyblish.api.InstancePlugin):
if not instance.data.get("settings_creator"):
return
- if "families" not in instance.data:
- instance.data["families"] = []
+ instance_label = instance.data["name"]
+ # Create instance's staging dir in temp
+ tmp_folder = tempfile.mkdtemp(prefix="traypublisher_")
+ instance.data["stagingDir"] = tmp_folder
+ instance.context.data["cleanupFullPaths"].append(tmp_folder)
- if "representations" not in instance.data:
- instance.data["representations"] = []
- repres = instance.data["representations"]
+ self.log.debug((
+ "Created temp staging directory for instance {}. {}"
+ ).format(instance_label, tmp_folder))
+
+ # Store filepaths for validation of their existence
+ source_filepaths = []
+ # Make sure there are no representations with same name
+ repre_names_counter = {}
+ # Store created names for logging
+ repre_names = []
+ # Store set of filepaths per each representation
+ representation_files_mapping = []
+ source = self._create_main_representations(
+ instance,
+ source_filepaths,
+ repre_names_counter,
+ repre_names,
+ representation_files_mapping
+ )
+
+ self._create_review_representation(
+ instance,
+ source_filepaths,
+ repre_names_counter,
+ repre_names,
+ representation_files_mapping
+ )
+
+ instance.data["source"] = source
+ instance.data["sourceFilepaths"] = list(set(source_filepaths))
+
+ self.log.debug(
+ (
+ "Created Simple Settings instance \"{}\""
+ " with {} representations: {}"
+ ).format(
+ instance_label,
+ len(instance.data["representations"]),
+ ", ".join(repre_names)
+ )
+ )
+
+ def _create_main_representations(
+ self,
+ instance,
+ source_filepaths,
+ repre_names_counter,
+ repre_names,
+ representation_files_mapping
+ ):
+ creator_attributes = instance.data["creator_attributes"]
+ filepath_items = creator_attributes["representation_files"]
+ if not isinstance(filepath_items, list):
+ filepath_items = [filepath_items]
+
+ source = None
+ for filepath_item in filepath_items:
+ # Skip if filepath item does not have filenames
+ if not filepath_item["filenames"]:
+ continue
+
+ filepaths = {
+ os.path.join(filepath_item["directory"], filename)
+ for filename in filepath_item["filenames"]
+ }
+ source_filepaths.extend(filepaths)
+
+ source = self._calculate_source(filepaths)
+ representation = self._create_representation_data(
+ filepath_item, repre_names_counter, repre_names
+ )
+ instance.data["representations"].append(representation)
+ representation_files_mapping.append(
+ (filepaths, representation, source)
+ )
+ return source
+
+ def _create_review_representation(
+ self,
+ instance,
+ source_filepaths,
+ repre_names_counter,
+ repre_names,
+ representation_files_mapping
+ ):
+ # Skip review representation creation if there are no representations
+ # created for "main" part
+ # - review representation must not be created in that case so
+ # validation can care about it
+ if not representation_files_mapping:
+ self.log.warning((
+ "There are missing source representations."
+ " Creation of review representation was skipped."
+ ))
+ return
creator_attributes = instance.data["creator_attributes"]
- filepath_item = creator_attributes["filepath"]
- self.log.info(filepath_item)
- filepaths = [
- os.path.join(filepath_item["directory"], filename)
- for filename in filepath_item["filenames"]
- ]
+ review_file_item = creator_attributes["reviewable"]
+ filenames = review_file_item.get("filenames")
+ if not filenames:
+ self.log.debug((
+ "Filepath for review is not defined."
+ " Skipping review representation creation."
+ ))
+ return
- instance.data["sourceFilepaths"] = filepaths
- instance.data["stagingDir"] = filepath_item["directory"]
+ filepaths = {
+ os.path.join(review_file_item["directory"], filename)
+ for filename in filenames
+ }
+ source_filepaths.extend(filepaths)
+ # First try to find out representation with same filepaths
+ # so it's not needed to create new representation just for review
+ review_representation = None
+ # Review path (only for logging)
+ review_path = None
+ for item in representation_files_mapping:
+ _filepaths, representation, repre_path = item
+ if _filepaths == filepaths:
+ review_representation = representation
+ review_path = repre_path
+ break
+
+ if review_representation is None:
+ self.log.debug("Creating new review representation")
+ review_path = self._calculate_source(filepaths)
+ review_representation = self._create_representation_data(
+ review_file_item, repre_names_counter, repre_names
+ )
+ instance.data["representations"].append(review_representation)
+
+ if "review" not in instance.data["families"]:
+ instance.data["families"].append("review")
+
+ review_representation["tags"].append("review")
+ self.log.debug("Representation {} was marked for review. {}".format(
+ review_representation["name"], review_path
+ ))
+
+ def _create_representation_data(
+ self, filepath_item, repre_names_counter, repre_names
+ ):
+ """Create new representation data based on file item.
+
+ Args:
+ filepath_item (Dict[str, Any]): Item with information about
+ representation paths.
+ repre_names_counter (Dict[str, int]): Store count of representation
+ names.
+ repre_names (List[str]): All used representation names. For
+ logging purposes.
+
+ Returns:
+ Dict: Prepared base representation data.
+ """
filenames = filepath_item["filenames"]
_, ext = os.path.splitext(filenames[0])
- ext = ext[1:]
if len(filenames) == 1:
filenames = filenames[0]
- repres.append({
- "ext": ext,
- "name": ext,
+ repre_name = repre_ext = ext[1:]
+ if repre_name not in repre_names_counter:
+ repre_names_counter[repre_name] = 2
+ else:
+ counter = repre_names_counter[repre_name]
+ repre_names_counter[repre_name] += 1
+ repre_name = "{}_{}".format(repre_name, counter)
+ repre_names.append(repre_name)
+ return {
+ "ext": repre_ext,
+ "name": repre_name,
"stagingDir": filepath_item["directory"],
- "files": filenames
- })
+ "files": filenames,
+ "tags": []
+ }
- self.log.debug("Created Simple Settings instance {}".format(
- instance.data
- ))
+ def _calculate_source(self, filepaths):
+ cols, rems = clique.assemble(filepaths)
+ if cols:
+ source = cols[0].format("{head}{padding}{tail}")
+ elif rems:
+ source = rems[0]
+ return source
diff --git a/openpype/hosts/traypublisher/plugins/publish/validate_filepaths.py b/openpype/hosts/traypublisher/plugins/publish/validate_filepaths.py
index c7302b1005..749199fbd3 100644
--- a/openpype/hosts/traypublisher/plugins/publish/validate_filepaths.py
+++ b/openpype/hosts/traypublisher/plugins/publish/validate_filepaths.py
@@ -3,8 +3,17 @@ import pyblish.api
from openpype.pipeline import PublishValidationError
-class ValidateWorkfilePath(pyblish.api.InstancePlugin):
- """Validate existence of workfile instance existence."""
+class ValidateFilePath(pyblish.api.InstancePlugin):
+ """Validate existence of source filepaths on instance.
+
+ Plugins looks into key 'sourceFilepaths' and validate if paths there
+ actually exist on disk.
+
+ Also validate if the key is filled but is empty. In that case also
+ crashes so do not fill the key if unfilled value should not cause error.
+
+ This is primarily created for Simple Creator instances.
+ """
label = "Validate Workfile"
order = pyblish.api.ValidatorOrder - 0.49
@@ -14,12 +23,28 @@ class ValidateWorkfilePath(pyblish.api.InstancePlugin):
def process(self, instance):
if "sourceFilepaths" not in instance.data:
self.log.info((
- "Can't validate source filepaths existence."
+ "Skipped validation of source filepaths existence."
" Instance does not have collected 'sourceFilepaths'"
))
return
- filepaths = instance.data.get("sourceFilepaths")
+ family = instance.data["family"]
+ label = instance.data["name"]
+ filepaths = instance.data["sourceFilepaths"]
+ if not filepaths:
+ raise PublishValidationError(
+ (
+ "Source filepaths of '{}' instance \"{}\" are not filled"
+ ).format(family, label),
+ "File not filled",
+ (
+ "## Files were not filled"
+ "\nThis mean that you didn't enter any files into required"
+ " file input."
+ "\n- Please refresh publishing and check instance"
+ " {}"
+ ).format(label)
+ )
not_found_files = [
filepath
@@ -34,11 +59,7 @@ class ValidateWorkfilePath(pyblish.api.InstancePlugin):
raise PublishValidationError(
(
"Filepath of '{}' instance \"{}\" does not exist:\n{}"
- ).format(
- instance.data["family"],
- instance.data["name"],
- joined_paths
- ),
+ ).format(family, label, joined_paths),
"File not found",
(
"## Files were not found\nFiles\n{}"
diff --git a/openpype/hosts/unreal/integration/UE_5.0/Content/__init__.py b/openpype/hosts/unreal/integration/UE_5.0/Content/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/openpype/lib/attribute_definitions.py b/openpype/lib/attribute_definitions.py
index a1f7c1e0f4..17658eef93 100644
--- a/openpype/lib/attribute_definitions.py
+++ b/openpype/lib/attribute_definitions.py
@@ -14,6 +14,7 @@ class AbstractAttrDefMeta(ABCMeta):
Each object of `AbtractAttrDef` mus have defined 'key' attribute.
"""
+
def __call__(self, *args, **kwargs):
obj = super(AbstractAttrDefMeta, self).__call__(*args, **kwargs)
init_class = getattr(obj, "__init__class__", None)
@@ -45,6 +46,7 @@ class AbtractAttrDef:
is_label_horizontal(bool): UI specific argument. Specify if label is
next to value input or ahead.
"""
+
is_value_def = True
def __init__(
@@ -77,6 +79,7 @@ class AbtractAttrDef:
Convert passed value to a valid type. Use default if value can't be
converted.
"""
+
pass
@@ -113,6 +116,7 @@ class UnknownDef(AbtractAttrDef):
This attribute can be used to keep existing data unchanged but does not
have known definition of type.
"""
+
def __init__(self, key, default=None, **kwargs):
kwargs["default"] = default
super(UnknownDef, self).__init__(key, **kwargs)
@@ -204,6 +208,7 @@ class TextDef(AbtractAttrDef):
placeholder(str): UI placeholder for attribute.
default(str, None): Default value. Empty string used when not defined.
"""
+
def __init__(
self, key, multiline=None, regex=None, placeholder=None, default=None,
**kwargs
@@ -531,14 +536,15 @@ class FileDef(AbtractAttrDef):
Args:
single_item(bool): Allow only single path item.
folders(bool): Allow folder paths.
- extensions(list): Allow files with extensions. Empty list will
+ extensions(List[str]): Allow files with extensions. Empty list will
allow all extensions and None will disable files completely.
- default(str, list): Defautl value.
+ extensions_label(str): Custom label shown instead of extensions in UI.
+ default(str, List[str]): Default value.
"""
def __init__(
self, key, single_item=True, folders=None, extensions=None,
- allow_sequences=True, default=None, **kwargs
+ allow_sequences=True, extensions_label=None, default=None, **kwargs
):
if folders is None and extensions is None:
folders = True
@@ -578,6 +584,7 @@ class FileDef(AbtractAttrDef):
self.folders = folders
self.extensions = set(extensions)
self.allow_sequences = allow_sequences
+ self.extensions_label = extensions_label
super(FileDef, self).__init__(key, default=default, **kwargs)
def __eq__(self, other):
diff --git a/openpype/lib/events.py b/openpype/lib/events.py
index 7bec6ee30d..301d62e2a6 100644
--- a/openpype/lib/events.py
+++ b/openpype/lib/events.py
@@ -11,6 +11,10 @@ except Exception:
from openpype.lib.python_2_comp import WeakMethod
+class MissingEventSystem(Exception):
+ pass
+
+
class EventCallback(object):
"""Callback registered to a topic.
@@ -176,16 +180,20 @@ class Event(object):
topic (str): Identifier of event.
data (Any): Data specific for event. Dictionary is recommended.
source (str): Identifier of source.
+ event_system (EventSystem): Event system in which can be event
+ triggered.
"""
+
_data = {}
- def __init__(self, topic, data=None, source=None):
+ def __init__(self, topic, data=None, source=None, event_system=None):
self._id = str(uuid4())
self._topic = topic
if data is None:
data = {}
self._data = data
self._source = source
+ self._event_system = event_system
def __getitem__(self, key):
return self._data[key]
@@ -211,28 +219,118 @@ class Event(object):
def emit(self):
"""Emit event and trigger callbacks."""
- StoredCallbacks.emit_event(self)
+ if self._event_system is None:
+ raise MissingEventSystem(
+ "Can't emit event {}. Does not have set event system.".format(
+ str(repr(self))
+ )
+ )
+ self._event_system.emit_event(self)
-class StoredCallbacks:
- _registered_callbacks = []
+class EventSystem(object):
+ """Encapsulate event handling into an object.
+
+ System wraps registered callbacks and triggered events into single object
+ so it is possible to create mutltiple independent systems that have their
+ topics and callbacks.
+
+
+ """
+
+ def __init__(self):
+ self._registered_callbacks = []
+
+ def add_callback(self, topic, callback):
+ """Register callback in event system.
+
+ Args:
+ topic (str): Topic for EventCallback.
+ callback (Callable): Function or method that will be called
+ when topic is triggered.
+
+ Returns:
+ EventCallback: Created callback object which can be used to
+ stop listening.
+ """
- @classmethod
- def add_callback(cls, topic, callback):
callback = EventCallback(topic, callback)
- cls._registered_callbacks.append(callback)
+ self._registered_callbacks.append(callback)
return callback
- @classmethod
- def emit_event(cls, event):
+ def create_event(self, topic, data, source):
+ """Create new event which is bound to event system.
+
+ Args:
+ topic (str): Event topic.
+ data (dict): Data related to event.
+ source (str): Source of event.
+
+ Returns:
+ Event: Object of event.
+ """
+
+ return Event(topic, data, source, self)
+
+ def emit(self, topic, data, source):
+ """Create event based on passed data and emit it.
+
+ This is easiest way how to trigger event in an event system.
+
+ Args:
+ topic (str): Event topic.
+ data (dict): Data related to event.
+ source (str): Source of event.
+
+ Returns:
+ Event: Created and emitted event.
+ """
+
+ event = self.create_event(topic, data, source)
+ event.emit()
+ return event
+
+ def emit_event(self, event):
+ """Emit event object.
+
+ Args:
+ event (Event): Prepared event with topic and data.
+ """
+
invalid_callbacks = []
- for callback in cls._registered_callbacks:
+ for callback in self._registered_callbacks:
callback.process_event(event)
if not callback.is_ref_valid:
invalid_callbacks.append(callback)
for callback in invalid_callbacks:
- cls._registered_callbacks.remove(callback)
+ self._registered_callbacks.remove(callback)
+
+
+class GlobalEventSystem:
+ """Event system living in global scope of process.
+
+ This is primarily used in host implementation to trigger events
+ related to DCC changes or changes of context in the host implementation.
+ """
+
+ _global_event_system = None
+
+ @classmethod
+ def get_global_event_system(cls):
+ if cls._global_event_system is None:
+ cls._global_event_system = EventSystem()
+ return cls._global_event_system
+
+ @classmethod
+ def add_callback(cls, topic, callback):
+ event_system = cls.get_global_event_system()
+ return event_system.add_callback(topic, callback)
+
+ @classmethod
+ def emit(cls, topic, data, source):
+ event_system = cls.get_global_event_system()
+ return event_system.emit(topic, data, source)
def register_event_callback(topic, callback):
@@ -249,7 +347,8 @@ def register_event_callback(topic, callback):
enable/disable listening to a topic or remove the callback from
the topic completely.
"""
- return StoredCallbacks.add_callback(topic, callback)
+
+ return GlobalEventSystem.add_callback(topic, callback)
def emit_event(topic, data=None, source=None):
@@ -263,6 +362,5 @@ def emit_event(topic, data=None, source=None):
Returns:
Event: Object of event that was emitted.
"""
- event = Event(topic, data, source)
- event.emit()
- return event
+
+ return GlobalEventSystem.emit(topic, data, source)
diff --git a/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py b/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py
index ebea8872f9..df914de854 100644
--- a/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py
+++ b/openpype/modules/ftrack/event_handlers_user/action_create_project_structure.py
@@ -84,6 +84,11 @@ class CreateProjectFolders(BaseAction):
create_project_folders(basic_paths, project_name)
self.create_ftrack_entities(basic_paths, project_entity)
+ self.trigger_event(
+ "openpype.project.structure.created",
+ {"project_name": project_name}
+ )
+
except Exception as exc:
self.log.warning("Creating of structure crashed.", exc_info=True)
session.rollback()
diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_note.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_note.py
index 952b21546d..77a7ebdfcf 100644
--- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_note.py
+++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_note.py
@@ -116,6 +116,7 @@ class IntegrateFtrackNote(pyblish.api.InstancePlugin):
"app_name": app_name,
"app_label": app_label,
"published_paths": "
".join(sorted(published_paths)),
+ "source": instance.data.get("source", '')
}
comment = template.format(**format_data)
if not comment:
diff --git a/openpype/modules/timers_manager/timers_manager.py b/openpype/modules/timers_manager/timers_manager.py
index 3cf1614316..3453e4bc4c 100644
--- a/openpype/modules/timers_manager/timers_manager.py
+++ b/openpype/modules/timers_manager/timers_manager.py
@@ -2,13 +2,13 @@ import os
import platform
+from openpype.client import get_asset_by_name
from openpype.modules import OpenPypeModule
from openpype_interfaces import (
ITrayService,
ILaunchHookPaths
)
from openpype.lib.events import register_event_callback
-from openpype.pipeline import AvalonMongoDB
from .exceptions import InvalidContextError
@@ -197,22 +197,13 @@ class TimersManager(OpenPypeModule, ITrayService, ILaunchHookPaths):
" Project: \"{}\" Asset: \"{}\" Task: \"{}\""
).format(str(project_name), str(asset_name), str(task_name)))
- dbconn = AvalonMongoDB()
- dbconn.install()
- dbconn.Session["AVALON_PROJECT"] = project_name
-
- asset_doc = dbconn.find_one(
- {
- "type": "asset",
- "name": asset_name
- },
- {
- "data.tasks": True,
- "data.parents": True
- }
+ asset_doc = get_asset_by_name(
+ project_name,
+ asset_name,
+ fields=["_id", "name", "data.tasks", "data.parents"]
)
+
if not asset_doc:
- dbconn.uninstall()
raise InvalidContextError((
"Asset \"{}\" not found in project \"{}\""
).format(asset_name, project_name))
@@ -220,7 +211,6 @@ class TimersManager(OpenPypeModule, ITrayService, ILaunchHookPaths):
asset_data = asset_doc.get("data") or {}
asset_tasks = asset_data.get("tasks") or {}
if task_name not in asset_tasks:
- dbconn.uninstall()
raise InvalidContextError((
"Task \"{}\" not found on asset \"{}\" in project \"{}\""
).format(task_name, asset_name, project_name))
@@ -238,9 +228,10 @@ class TimersManager(OpenPypeModule, ITrayService, ILaunchHookPaths):
hierarchy_items = asset_data.get("parents") or []
hierarchy_items.append(asset_name)
- dbconn.uninstall()
return {
"project_name": project_name,
+ "asset_id": str(asset_doc["_id"]),
+ "asset_name": asset_doc["name"],
"task_name": task_name,
"task_type": task_type,
"hierarchy": hierarchy_items
diff --git a/openpype/pipeline/create/context.py b/openpype/pipeline/create/context.py
index aecdb04635..9b55c3b21e 100644
--- a/openpype/pipeline/create/context.py
+++ b/openpype/pipeline/create/context.py
@@ -29,6 +29,7 @@ UpdateData = collections.namedtuple("UpdateData", ["instance", "changes"])
class ImmutableKeyError(TypeError):
"""Accessed key is immutable so does not allow changes or removements."""
+
def __init__(self, key, msg=None):
self.immutable_key = key
if not msg:
@@ -40,6 +41,7 @@ class ImmutableKeyError(TypeError):
class HostMissRequiredMethod(Exception):
"""Host does not have implemented required functions for creation."""
+
def __init__(self, host, missing_methods):
self.missing_methods = missing_methods
self.host = host
@@ -66,6 +68,7 @@ class InstanceMember:
TODO:
Implement and use!
"""
+
def __init__(self, instance, name):
self.instance = instance
@@ -94,6 +97,7 @@ class AttributeValues:
values(dict): Values after possible conversion.
origin_data(dict): Values loaded from host before conversion.
"""
+
def __init__(self, attr_defs, values, origin_data=None):
from openpype.lib.attribute_definitions import UnknownDef
@@ -174,6 +178,10 @@ class AttributeValues:
output = {}
for key in self._data:
output[key] = self[key]
+
+ for key, attr_def in self._attr_defs_by_key.items():
+ if key not in output:
+ output[key] = attr_def.default
return output
@staticmethod
@@ -196,6 +204,7 @@ class CreatorAttributeValues(AttributeValues):
Args:
instance (CreatedInstance): Instance for which are values hold.
"""
+
def __init__(self, instance, *args, **kwargs):
self.instance = instance
super(CreatorAttributeValues, self).__init__(*args, **kwargs)
@@ -211,6 +220,7 @@ class PublishAttributeValues(AttributeValues):
publish_attributes(PublishAttributes): Wrapper for multiple publish
attributes is used as parent object.
"""
+
def __init__(self, publish_attributes, *args, **kwargs):
self.publish_attributes = publish_attributes
super(PublishAttributeValues, self).__init__(*args, **kwargs)
@@ -232,6 +242,7 @@ class PublishAttributes:
attr_plugins(list): List of publish plugins that may have defined
attribute definitions.
"""
+
def __init__(self, parent, origin_data, attr_plugins=None):
self.parent = parent
self._origin_data = copy.deepcopy(origin_data)
@@ -270,6 +281,7 @@ class PublishAttributes:
key(str): Plugin name.
default: Default value if plugin was not found.
"""
+
if key not in self._data:
return default
@@ -287,11 +299,13 @@ class PublishAttributes:
def plugin_names_order(self):
"""Plugin names order by their 'order' attribute."""
+
for name in self._plugin_names_order:
yield name
def data_to_store(self):
"""Convert attribute values to "data to store"."""
+
output = {}
for key, attr_value in self._data.items():
output[key] = attr_value.data_to_store()
@@ -299,6 +313,7 @@ class PublishAttributes:
def changes(self):
"""Return changes per each key."""
+
changes = {}
for key, attr_val in self._data.items():
attr_changes = attr_val.changes()
@@ -314,6 +329,7 @@ class PublishAttributes:
def set_publish_plugins(self, attr_plugins):
"""Set publish plugins attribute definitions."""
+
self._plugin_names_order = []
self._missing_plugins = []
self.attr_plugins = attr_plugins or []
@@ -365,6 +381,7 @@ class CreatedInstance:
`openpype.pipeline.registered_host`.
new(bool): Is instance new.
"""
+
# Keys that can't be changed or removed from data after loading using
# creator.
# - 'creator_attributes' and 'publish_attributes' can change values of
@@ -566,6 +583,7 @@ class CreatedInstance:
@property
def id(self):
"""Instance identifier."""
+
return self._data["instance_id"]
@property
@@ -574,10 +592,12 @@ class CreatedInstance:
Access to data is needed to modify values.
"""
+
return self
def changes(self):
"""Calculate and return changes."""
+
changes = {}
new_keys = set()
for key, new_value in self._data.items():
@@ -716,6 +736,7 @@ class CreateContext:
self.manual_creators = {}
self.publish_discover_result = None
+ self.publish_plugins_mismatch_targets = []
self.publish_plugins = []
self.plugins_with_defs = []
self._attr_plugins_by_family = {}
@@ -838,6 +859,7 @@ class CreateContext:
discover_result = DiscoverResult()
plugins_with_defs = []
plugins_by_targets = []
+ plugins_mismatch_targets = []
if discover_publish_plugins:
discover_result = publish_plugins_discover()
publish_plugins = discover_result.plugins
@@ -847,11 +869,19 @@ class CreateContext:
plugins_by_targets = pyblish.logic.plugins_by_targets(
publish_plugins, list(targets)
)
+
# Collect plugins that can have attribute definitions
for plugin in publish_plugins:
if OpenPypePyblishPluginMixin in inspect.getmro(plugin):
plugins_with_defs.append(plugin)
+ plugins_mismatch_targets = [
+ plugin
+ for plugin in publish_plugins
+ if plugin not in plugins_by_targets
+ ]
+
+ self.publish_plugins_mismatch_targets = plugins_mismatch_targets
self.publish_discover_result = discover_result
self.publish_plugins = plugins_by_targets
self.plugins_with_defs = plugins_with_defs
diff --git a/openpype/pipeline/create/creator_plugins.py b/openpype/pipeline/create/creator_plugins.py
index 91b9d80234..52c76db5ef 100644
--- a/openpype/pipeline/create/creator_plugins.py
+++ b/openpype/pipeline/create/creator_plugins.py
@@ -102,6 +102,10 @@ class BaseCreator:
return self.create_context.project_name
+ @property
+ def host(self):
+ return self.create_context.host
+
def get_group_label(self):
"""Group label under which are instances grouped in UI.
diff --git a/openpype/plugins/publish/collect_anatomy_instance_data.py b/openpype/plugins/publish/collect_anatomy_instance_data.py
index c75534cf83..f67d3373d9 100644
--- a/openpype/plugins/publish/collect_anatomy_instance_data.py
+++ b/openpype/plugins/publish/collect_anatomy_instance_data.py
@@ -51,6 +51,7 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
project_name = legacy_io.active_project()
self.fill_missing_asset_docs(context, project_name)
+ self.fill_instance_data_from_asset(context)
self.fill_latest_versions(context, project_name)
self.fill_anatomy_data(context)
@@ -115,6 +116,23 @@ class CollectAnatomyInstanceData(pyblish.api.ContextPlugin):
"Not found asset documents with names \"{}\"."
).format(joined_asset_names))
+ def fill_instance_data_from_asset(self, context):
+ for instance in context:
+ asset_doc = instance.data.get("assetEntity")
+ if not asset_doc:
+ continue
+
+ asset_data = asset_doc["data"]
+ for key in (
+ "fps",
+ "frameStart",
+ "frameEnd",
+ "handleStart",
+ "handleEnd",
+ ):
+ if key not in instance.data and key in asset_data:
+ instance.data[key] = asset_data[key]
+
def fill_latest_versions(self, context, project_name):
"""Try to find latest version for each instance's subset.
diff --git a/openpype/plugins/publish/collect_cleanup_keys.py b/openpype/plugins/publish/collect_cleanup_keys.py
index 635b038387..b9cd1a9fc9 100644
--- a/openpype/plugins/publish/collect_cleanup_keys.py
+++ b/openpype/plugins/publish/collect_cleanup_keys.py
@@ -14,7 +14,7 @@ class CollectCleanupKeys(pyblish.api.ContextPlugin):
"""Prepare keys for 'ExplicitCleanUp' plugin."""
label = "Collect Cleanup Keys"
- order = pyblish.api.CollectorOrder
+ order = pyblish.api.CollectorOrder - 0.5
def process(self, context):
context.data["cleanupFullPaths"] = []
diff --git a/openpype/plugins/publish/collect_from_create_context.py b/openpype/plugins/publish/collect_from_create_context.py
index f6ead98809..d2be633cbe 100644
--- a/openpype/plugins/publish/collect_from_create_context.py
+++ b/openpype/plugins/publish/collect_from_create_context.py
@@ -47,12 +47,11 @@ class CollectFromCreateContext(pyblish.api.ContextPlugin):
"label": subset,
"name": subset,
"family": in_data["family"],
- "families": instance_families
+ "families": instance_families,
+ "representations": []
})
for key, value in in_data.items():
if key not in instance.data:
instance.data[key] = value
self.log.info("collected instance: {}".format(instance.data))
self.log.info("parsing data: {}".format(in_data))
-
- instance.data["representations"] = list()
diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json
index 9bebd92cb9..5976c6a823 100644
--- a/openpype/settings/defaults/project_settings/maya.json
+++ b/openpype/settings/defaults/project_settings/maya.json
@@ -497,11 +497,29 @@
"override_viewport_options": true,
"displayLights": "default",
"textureMaxResolution": 1024,
- "multiSample": 4,
+ "renderDepthOfField": true,
"shadows": true,
"textures": true,
"twoSidedLighting": true,
- "ssaoEnable": true,
+ "lineAAEnable": true,
+ "multiSample": 8,
+ "ssaoEnable": false,
+ "ssaoAmount": 1,
+ "ssaoRadius": 16,
+ "ssaoFilterRadius": 16,
+ "ssaoSamples": 16,
+ "fogging": false,
+ "hwFogFalloff": "0",
+ "hwFogDensity": 0.0,
+ "hwFogStart": 0,
+ "hwFogEnd": 100,
+ "hwFogAlpha": 0,
+ "hwFogColorR": 1.0,
+ "hwFogColorG": 1.0,
+ "hwFogColorB": 1.0,
+ "motionBlurEnable": false,
+ "motionBlurSampleCount": 8,
+ "motionBlurShutterOpenFraction": 0.2,
"cameras": false,
"clipGhosts": false,
"controlVertices": false,
diff --git a/openpype/settings/defaults/project_settings/nuke.json b/openpype/settings/defaults/project_settings/nuke.json
index 6c45e2a9c1..3e29122074 100644
--- a/openpype/settings/defaults/project_settings/nuke.json
+++ b/openpype/settings/defaults/project_settings/nuke.json
@@ -287,7 +287,11 @@
"LoadClip": {
"enabled": true,
"_representations": [],
- "node_name_template": "{class_name}_{ext}"
+ "node_name_template": "{class_name}_{ext}",
+ "options_defaults": {
+ "start_at_workfile": true,
+ "add_retime": true
+ }
}
},
"workfile_builder": {
diff --git a/openpype/settings/defaults/project_settings/traypublisher.json b/openpype/settings/defaults/project_settings/traypublisher.json
index cb3d3d1d1a..cbe58f49d6 100644
--- a/openpype/settings/defaults/project_settings/traypublisher.json
+++ b/openpype/settings/defaults/project_settings/traypublisher.json
@@ -8,9 +8,10 @@
"default_variants": [
"Main"
],
- "description": "Publish workfile backup",
- "detailed_description": "",
- "allow_sequences": true,
+ "description": "Backup of a working scene",
+ "detailed_description": "Workfiles are full scenes from any application that are directly edited by artists. They represent a state of work on a task at a given point and are usually not directly referenced into other scenes.",
+ "allow_sequences": false,
+ "allow_multiple_items": false,
"extensions": [
".ma",
".mb",
@@ -30,6 +31,209 @@
".psb",
".aep"
]
+ },
+ {
+ "family": "model",
+ "identifier": "",
+ "label": "Model",
+ "icon": "fa.cubes",
+ "default_variants": [
+ "Main",
+ "Proxy",
+ "Sculpt"
+ ],
+ "description": "Clean models",
+ "detailed_description": "Models should only contain geometry data, without any extras like cameras, locators or bones.\n\nKeep in mind that models published from tray publisher are not validated for correctness. ",
+ "allow_sequences": false,
+ "allow_multiple_items": true,
+ "extensions": [
+ ".ma",
+ ".mb",
+ ".obj",
+ ".abc",
+ ".fbx",
+ ".bgeo",
+ ".bgeogz",
+ ".bgeosc",
+ ".usd",
+ ".blend"
+ ]
+ },
+ {
+ "family": "pointcache",
+ "identifier": "",
+ "label": "Pointcache",
+ "icon": "fa.gears",
+ "default_variants": [
+ "Main"
+ ],
+ "description": "Geometry Caches",
+ "detailed_description": "Alembic or bgeo cache of animated data",
+ "allow_sequences": true,
+ "allow_multiple_items": true,
+ "extensions": [
+ ".abc",
+ ".bgeo",
+ ".bgeogz",
+ ".bgeosc"
+ ]
+ },
+ {
+ "family": "plate",
+ "identifier": "",
+ "label": "Plate",
+ "icon": "mdi.camera-image",
+ "default_variants": [
+ "Main",
+ "BG",
+ "Animatic",
+ "Reference",
+ "Offline"
+ ],
+ "description": "Footage Plates",
+ "detailed_description": "Any type of image seqeuence coming from outside of the studio. Usually camera footage, but could also be animatics used for reference.",
+ "allow_sequences": true,
+ "allow_multiple_items": true,
+ "extensions": [
+ ".exr",
+ ".png",
+ ".dpx",
+ ".jpg",
+ ".tiff",
+ ".tif",
+ ".mov",
+ ".mp4",
+ ".avi"
+ ]
+ },
+ {
+ "family": "render",
+ "identifier": "",
+ "label": "Render",
+ "icon": "mdi.folder-multiple-image",
+ "default_variants": [],
+ "description": "Rendered images or video",
+ "detailed_description": "Sequence or single file renders",
+ "allow_sequences": true,
+ "allow_multiple_items": true,
+ "extensions": [
+ ".exr",
+ ".png",
+ ".dpx",
+ ".jpg",
+ ".jpeg",
+ ".tiff",
+ ".tif",
+ ".mov",
+ ".mp4",
+ ".avi"
+ ]
+ },
+ {
+ "family": "camera",
+ "identifier": "",
+ "label": "Camera",
+ "icon": "fa.video-camera",
+ "default_variants": [],
+ "description": "3d Camera",
+ "detailed_description": "Ideally this should be only camera itself with baked animation, however, it can technically also include helper geometry.",
+ "allow_sequences": false,
+ "allow_multiple_items": true,
+ "extensions": [
+ ".abc",
+ ".ma",
+ ".hip",
+ ".blend",
+ ".fbx",
+ ".usd"
+ ]
+ },
+ {
+ "family": "image",
+ "identifier": "",
+ "label": "Image",
+ "icon": "fa.image",
+ "default_variants": [
+ "Reference",
+ "Texture",
+ "Concept",
+ "Background"
+ ],
+ "description": "Single image",
+ "detailed_description": "Any image data can be published as image family. References, textures, concept art, matte paints. This is a fallback 2d family for everything that doesn't fit more specific family.",
+ "allow_sequences": false,
+ "allow_multiple_items": true,
+ "extensions": [
+ ".exr",
+ ".jpg",
+ ".jpeg",
+ ".dpx",
+ ".bmp",
+ ".tif",
+ ".tiff",
+ ".png",
+ ".psb",
+ ".psd"
+ ]
+ },
+ {
+ "family": "vdb",
+ "identifier": "",
+ "label": "VDB Volumes",
+ "icon": "fa.cloud",
+ "default_variants": [],
+ "description": "Sparse volumetric data",
+ "detailed_description": "Hierarchical data structure for the efficient storage and manipulation of sparse volumetric data discretized on three-dimensional grids",
+ "allow_sequences": true,
+ "allow_multiple_items": true,
+ "extensions": [
+ ".vdb"
+ ]
+ },
+ {
+ "family": "matchmove",
+ "identifier": "",
+ "label": "Matchmove",
+ "icon": "fa.empire",
+ "default_variants": [
+ "Camera",
+ "Object",
+ "Mocap"
+ ],
+ "description": "Matchmoving script",
+ "detailed_description": "Script exported from matchmoving application to be later processed into a tracked camera with additional data",
+ "allow_sequences": false,
+ "allow_multiple_items": true,
+ "extensions": []
+ },
+ {
+ "family": "rig",
+ "identifier": "",
+ "label": "Rig",
+ "icon": "fa.wheelchair",
+ "default_variants": [],
+ "description": "CG rig file",
+ "detailed_description": "CG rigged character or prop. Rig should be clean of any extra data and directly loadable into it's respective application\t",
+ "allow_sequences": false,
+ "allow_multiple_items": false,
+ "extensions": [
+ ".ma",
+ ".blend",
+ ".hip",
+ ".hda"
+ ]
+ },
+ {
+ "family": "simpleUnrealTexture",
+ "identifier": "",
+ "label": "Simple UE texture",
+ "icon": "fa.image",
+ "default_variants": [],
+ "description": "Simple Unreal Engine texture",
+ "detailed_description": "Texture files with Unreal Engine naming conventions",
+ "allow_sequences": false,
+ "allow_multiple_items": true,
+ "extensions": []
}
],
"BatchMovCreator": {
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json
index f8f9d5093d..c0069dcdab 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_ftrack.json
@@ -822,7 +822,7 @@
},
{
"type": "label",
- "label": "Template may contain formatting keys intent, comment, host_name, app_name, app_label and published_paths."
+ "label": "Template may contain formatting keys intent, comment, host_name, app_name, app_label, published_paths and source."
},
{
"type": "text",
diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json
index d4ad57767a..50ba246c97 100644
--- a/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json
+++ b/openpype/settings/entities/schemas/projects_schema/schema_project_traypublisher.json
@@ -67,6 +67,11 @@
"label": "Allow sequences",
"type": "boolean"
},
+ {
+ "key": "allow_multiple_items",
+ "label": "Allow multiple items",
+ "type": "boolean"
+ },
{
"type": "list",
"key": "extensions",
diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json
index d6b81c8687..7a40f349cc 100644
--- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json
+++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json
@@ -202,12 +202,15 @@
"decimal": 0
},
{
- "type": "number",
- "key": "multiSample",
- "label": "Anti Aliasing Samples",
- "decimal": 0,
- "minimum": 0,
- "maximum": 32
+ "type": "splitter"
+ },
+ {
+ "type":"boolean",
+ "key": "renderDepthOfField",
+ "label": "Depth of Field"
+ },
+ {
+ "type": "splitter"
},
{
"type": "boolean",
@@ -224,11 +227,145 @@
"key": "twoSidedLighting",
"label": "Two Sided Lighting"
},
+ {
+ "type": "splitter"
+ },
+ {
+ "type": "boolean",
+ "key": "lineAAEnable",
+ "label": "Enable Anti-Aliasing"
+ },
+ {
+ "type": "number",
+ "key": "multiSample",
+ "label": "Anti Aliasing Samples",
+ "decimal": 0,
+ "minimum": 0,
+ "maximum": 32
+ },
+ {
+ "type": "splitter"
+ },
{
"type": "boolean",
"key": "ssaoEnable",
"label": "Screen Space Ambient Occlusion"
},
+ {
+ "type": "number",
+ "key": "ssaoAmount",
+ "label": "SSAO Amount"
+ },
+ {
+ "type": "number",
+ "key": "ssaoRadius",
+ "label": "SSAO Radius"
+ },
+ {
+ "type": "number",
+ "key": "ssaoFilterRadius",
+ "label": "SSAO Filter Radius",
+ "decimal": 0,
+ "minimum": 1,
+ "maximum": 32
+ },
+ {
+ "type": "number",
+ "key": "ssaoSamples",
+ "label": "SSAO Samples",
+ "decimal": 0,
+ "minimum": 8,
+ "maximum": 32
+ },
+ {
+ "type": "splitter"
+ },
+ {
+ "type": "boolean",
+ "key": "fogging",
+ "label": "Enable Hardware Fog"
+ },
+ {
+ "type": "enum",
+ "key": "hwFogFalloff",
+ "label": "Hardware Falloff",
+ "enum_items": [
+ { "0": "Linear"},
+ { "1": "Exponential"},
+ { "2": "Exponential Squared"}
+ ]
+ },
+ {
+ "type": "number",
+ "key": "hwFogDensity",
+ "label": "Fog Density",
+ "decimal": 2,
+ "minimum": 0,
+ "maximum": 1
+ },
+ {
+ "type": "number",
+ "key": "hwFogStart",
+ "label": "Fog Start"
+ },
+ {
+ "type": "number",
+ "key": "hwFogEnd",
+ "label": "Fog End"
+ },
+ {
+ "type": "number",
+ "key": "hwFogAlpha",
+ "label": "Fog Alpha"
+ },
+ {
+ "type": "number",
+ "key": "hwFogColorR",
+ "label": "Fog Color R",
+ "decimal": 2,
+ "minimum": 0,
+ "maximum": 1
+ },
+ {
+ "type": "number",
+ "key": "hwFogColorG",
+ "label": "Fog Color G",
+ "decimal": 2,
+ "minimum": 0,
+ "maximum": 1
+ },
+ {
+ "type": "number",
+ "key": "hwFogColorB",
+ "label": "Fog Color B",
+ "decimal": 2,
+ "minimum": 0,
+ "maximum": 1
+ },
+ {
+ "type": "splitter"
+ },
+ {
+ "type": "boolean",
+ "key": "motionBlurEnable",
+ "label": "Enable Motion Blur"
+ },
+ {
+ "type": "number",
+ "key": "motionBlurSampleCount",
+ "label": "Motion Blur Sample Count",
+ "decimal": 0,
+ "minimum": 8,
+ "maximum": 32
+ },
+ {
+ "type": "number",
+ "key": "motionBlurShutterOpenFraction",
+ "label": "Shutter Open Fraction",
+ "decimal": 3,
+ "minimum": 0.01,
+ "maximum": 32
+ },
{
"type": "splitter"
},
diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json
index 5bd8337e4c..805424c632 100644
--- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json
+++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_nuke_load.json
@@ -11,10 +11,52 @@
{
"key": "LoadImage",
"label": "Image Loader"
+ }
+ ]
+ },
+ {
+ "type": "dict",
+ "collapsible": true,
+ "key": "LoadClip",
+ "label": "Clip Loader",
+ "checkbox_key": "enabled",
+ "children": [
+ {
+ "type": "boolean",
+ "key": "enabled",
+ "label": "Enabled"
},
{
- "key": "LoadClip",
- "label": "Clip Loader"
+ "type": "list",
+ "key": "_representations",
+ "label": "Representations",
+ "object_type": "text"
+ },
+ {
+ "type": "text",
+ "key": "node_name_template",
+ "label": "Node name template"
+ },
+ {
+ "type": "splitter"
+ },
+ {
+ "type": "dict",
+ "collapsible": false,
+ "key": "options_defaults",
+ "label": "Loader option defaults",
+ "children": [
+ {
+ "type": "boolean",
+ "key": "start_at_workfile",
+ "label": "Start at worfile beggining"
+ },
+ {
+ "type": "boolean",
+ "key": "add_retime",
+ "label": "Add retime"
+ }
+ ]
}
]
}
diff --git a/openpype/tools/publisher/control.py b/openpype/tools/publisher/control.py
index 915fb7f32e..b48bb61386 100644
--- a/openpype/tools/publisher/control.py
+++ b/openpype/tools/publisher/control.py
@@ -154,15 +154,20 @@ class PublishReport:
self._all_instances_by_id = {}
self._current_context = None
- def reset(self, context, publish_discover_result=None):
+ def reset(self, context, create_context):
"""Reset report and clear all data."""
- self._publish_discover_result = publish_discover_result
+
+ self._publish_discover_result = create_context.publish_discover_result
self._plugin_data = []
self._plugin_data_with_plugin = []
self._current_plugin_data = {}
self._all_instances_by_id = {}
self._current_context = context
+ for plugin in create_context.publish_plugins_mismatch_targets:
+ plugin_data = self._add_plugin_data_item(plugin)
+ plugin_data["skipped"] = True
+
def add_plugin_iter(self, plugin, context):
"""Add report about single iteration of plugin."""
for instance in context:
@@ -205,6 +210,7 @@ class PublishReport:
"name": plugin.__name__,
"label": label,
"order": plugin.order,
+ "targets": list(plugin.targets),
"instances_data": [],
"actions_data": [],
"skipped": False,
@@ -569,6 +575,8 @@ class PublisherController:
# Stop publishing
self.stop_publish()
+ self.save_changes()
+
# Reset avalon context
self.create_context.reset_avalon_context()
@@ -777,10 +785,7 @@ class PublisherController:
# - pop the key after first collector using it would be safest option?
self._publish_context.data["create_context"] = self.create_context
- self._publish_report.reset(
- self._publish_context,
- self.create_context.publish_discover_result
- )
+ self._publish_report.reset(self._publish_context, self.create_context)
self._publish_validation_errors = []
self._publish_current_plugin_validation_errors = None
self._publish_error = None
diff --git a/openpype/tools/publisher/publish_report_viewer/report_items.py b/openpype/tools/publisher/publish_report_viewer/report_items.py
index b47d14da25..8a01569723 100644
--- a/openpype/tools/publisher/publish_report_viewer/report_items.py
+++ b/openpype/tools/publisher/publish_report_viewer/report_items.py
@@ -83,10 +83,8 @@ class PublishReport:
logs = []
plugins_items_by_id = {}
- plugins_id_order = []
for plugin_data in data["plugins_data"]:
item = PluginItem(plugin_data)
- plugins_id_order.append(item.id)
plugins_items_by_id[item.id] = item
for instance_data_item in plugin_data["instances_data"]:
instance_id = instance_data_item["id"]
@@ -95,6 +93,14 @@ class PublishReport:
copy.deepcopy(log_item_data), item.id, instance_id
)
logs.append(log_item)
+ sorted_plugins = sorted(
+ plugins_items_by_id.values(),
+ key=lambda item: item.order
+ )
+ plugins_id_order = [
+ plugin_item.id
+ for plugin_item in sorted_plugins
+ ]
logs_by_instance_id = collections.defaultdict(list)
for log_item in logs:
diff --git a/openpype/tools/publisher/publish_report_viewer/widgets.py b/openpype/tools/publisher/publish_report_viewer/widgets.py
index fd226ea0e4..61eb814a56 100644
--- a/openpype/tools/publisher/publish_report_viewer/widgets.py
+++ b/openpype/tools/publisher/publish_report_viewer/widgets.py
@@ -1,3 +1,4 @@
+from math import ceil
from Qt import QtWidgets, QtCore, QtGui
from openpype.widgets.nice_checkbox import NiceCheckbox
@@ -137,13 +138,75 @@ class PluginLoadReportWidget(QtWidgets.QWidget):
self._model.set_report(report)
+class ZoomPlainText(QtWidgets.QPlainTextEdit):
+ def __init__(self, *args, **kwargs):
+ super(ZoomPlainText, self).__init__(*args, **kwargs)
+
+ anim_timer = QtCore.QTimer()
+ anim_timer.setInterval(20)
+
+ anim_timer.timeout.connect(self._scaling_callback)
+
+ self._anim_timer = anim_timer
+ self._zoom_enabled = False
+ self._scheduled_scalings = 0
+ self._point_size = None
+
+ def wheelEvent(self, event):
+ if not self._zoom_enabled:
+ super(ZoomPlainText, self).wheelEvent(event)
+ return
+
+ degrees = float(event.delta()) / 8
+ steps = int(ceil(degrees / 5))
+ self._scheduled_scalings += steps
+ if (self._scheduled_scalings * steps < 0):
+ self._scheduled_scalings = steps
+
+ self._anim_timer.start()
+
+ def _scaling_callback(self):
+ if self._scheduled_scalings == 0:
+ self._anim_timer.stop()
+ return
+
+ factor = 1.0 + (self._scheduled_scalings / 300)
+ font = self.font()
+ if self._point_size is None:
+ self._point_size = font.pointSizeF()
+
+ self._point_size *= factor
+ if self._point_size < 1:
+ self._point_size = 1.0
+
+ font.setPointSizeF(self._point_size)
+ # Using 'self.setFont(font)' would not be propagated when stylesheets
+ # are applied on this widget
+ self.setStyleSheet("font-size: {}pt".format(font.pointSize()))
+
+ if self._scheduled_scalings > 0:
+ self._scheduled_scalings -= 1
+ else:
+ self._scheduled_scalings += 1
+
+ def keyPressEvent(self, event):
+ if event.key() == QtCore.Qt.Key_Control:
+ self._zoom_enabled = True
+ super(ZoomPlainText, self).keyPressEvent(event)
+
+ def keyReleaseEvent(self, event):
+ if event.key() == QtCore.Qt.Key_Control:
+ self._zoom_enabled = False
+ super(ZoomPlainText, self).keyReleaseEvent(event)
+
+
class DetailsWidget(QtWidgets.QWidget):
def __init__(self, parent):
super(DetailsWidget, self).__init__(parent)
- output_widget = QtWidgets.QPlainTextEdit(self)
- output_widget.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
+ output_widget = ZoomPlainText(self)
output_widget.setObjectName("PublishLogConsole")
+ output_widget.setTextInteractionFlags(QtCore.Qt.TextBrowserInteraction)
layout = QtWidgets.QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
diff --git a/openpype/tools/settings/settings/categories.py b/openpype/tools/settings/settings/categories.py
index 764f42f1a3..f42027d9e2 100644
--- a/openpype/tools/settings/settings/categories.py
+++ b/openpype/tools/settings/settings/categories.py
@@ -854,6 +854,9 @@ class ProjectWidget(SettingsCategoryWidget):
project_list_widget.version_change_requested.connect(
self._on_source_version_change
)
+ project_list_widget.extract_to_file_requested.connect(
+ self._on_extract_to_file
+ )
self.project_list_widget = project_list_widget
diff --git a/openpype/tools/settings/settings/widgets.py b/openpype/tools/settings/settings/widgets.py
index 45c21d5685..88d923c16a 100644
--- a/openpype/tools/settings/settings/widgets.py
+++ b/openpype/tools/settings/settings/widgets.py
@@ -1008,6 +1008,7 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel):
class ProjectListWidget(QtWidgets.QWidget):
project_changed = QtCore.Signal()
version_change_requested = QtCore.Signal(str)
+ extract_to_file_requested = QtCore.Signal()
def __init__(self, parent, only_active=False):
self._parent = parent
@@ -1099,7 +1100,12 @@ class ProjectListWidget(QtWidgets.QWidget):
self.version_change_requested
)
submenu.addAction(action)
+
+ extract_action = QtWidgets.QAction("Extract to file", menu)
+ extract_action.triggered.connect(self.extract_to_file_requested)
+
menu.addMenu(submenu)
+ menu.addAction(extract_action)
menu.exec_(QtGui.QCursor.pos())
def on_item_clicked(self, new_index):
diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py
index 5934c4aa8a..cc33287091 100644
--- a/openpype/tools/traypublisher/window.py
+++ b/openpype/tools/traypublisher/window.py
@@ -12,9 +12,7 @@ from openpype.pipeline import (
install_host,
AvalonMongoDB,
)
-from openpype.hosts.traypublisher import (
- api as traypublisher
-)
+from openpype.hosts.traypublisher.api import TrayPublisherHost
from openpype.tools.publisher import PublisherWindow
from openpype.tools.utils.constants import PROJECT_NAME_ROLE
from openpype.tools.utils.models import (
@@ -111,9 +109,13 @@ class StandaloneOverlayWidget(QtWidgets.QFrame):
if project_name:
self._set_project(project_name)
+ @property
+ def host(self):
+ return self._publisher_window.controller.host
+
def _set_project(self, project_name):
self._project_name = project_name
- traypublisher.set_project_name(project_name)
+ self.host.set_project_name(project_name)
self.setVisible(False)
self.project_selected.emit(project_name)
@@ -190,7 +192,8 @@ class TrayPublishWindow(PublisherWindow):
def main():
- install_host(traypublisher)
+ host = TrayPublisherHost()
+ install_host(host)
app = QtWidgets.QApplication([])
window = TrayPublishWindow()
window.show()
diff --git a/openpype/vendor/python/common/capture.py b/openpype/vendor/python/common/capture.py
index 6b4c40a6e8..4d9e1da3e4 100644
--- a/openpype/vendor/python/common/capture.py
+++ b/openpype/vendor/python/common/capture.py
@@ -380,7 +380,8 @@ Viewport2Options = {
"transparencyAlgorithm": 1,
"transparencyQuality": 0.33,
"useMaximumHardwareLights": True,
- "vertexAnimationCache": 0
+ "vertexAnimationCache": 0,
+ "renderDepthOfField": 0
}
diff --git a/openpype/version.py b/openpype/version.py
index 3239b0e2a2..c7b0de0381 100644
--- a/openpype/version.py
+++ b/openpype/version.py
@@ -1,3 +1,3 @@
# -*- coding: utf-8 -*-
"""Package declaring Pype version."""
-__version__ = "3.12.1-nightly.4"
+__version__ = "3.12.1"
diff --git a/openpype/widgets/attribute_defs/files_widget.py b/openpype/widgets/attribute_defs/files_widget.py
index 698a91a1a5..d29aa1b607 100644
--- a/openpype/widgets/attribute_defs/files_widget.py
+++ b/openpype/widgets/attribute_defs/files_widget.py
@@ -1,6 +1,7 @@
import os
import collections
import uuid
+import json
from Qt import QtWidgets, QtCore, QtGui
@@ -26,6 +27,27 @@ IS_SEQUENCE_ROLE = QtCore.Qt.UserRole + 7
EXT_ROLE = QtCore.Qt.UserRole + 8
+def convert_bytes_to_json(bytes_value):
+ if isinstance(bytes_value, QtCore.QByteArray):
+ # Raw data are already QByteArray and we don't have to load them
+ encoded_data = bytes_value
+ else:
+ encoded_data = QtCore.QByteArray.fromRawData(bytes_value)
+ stream = QtCore.QDataStream(encoded_data, QtCore.QIODevice.ReadOnly)
+ text = stream.readQString()
+ try:
+ return json.loads(text)
+ except Exception:
+ return None
+
+
+def convert_data_to_bytes(data):
+ bytes_value = QtCore.QByteArray()
+ stream = QtCore.QDataStream(bytes_value, QtCore.QIODevice.WriteOnly)
+ stream.writeQString(json.dumps(data))
+ return bytes_value
+
+
class SupportLabel(QtWidgets.QLabel):
pass
@@ -33,7 +55,7 @@ class SupportLabel(QtWidgets.QLabel):
class DropEmpty(QtWidgets.QWidget):
_empty_extensions = "Any file"
- def __init__(self, single_item, allow_sequences, parent):
+ def __init__(self, single_item, allow_sequences, extensions_label, parent):
super(DropEmpty, self).__init__(parent)
drop_label_widget = QtWidgets.QLabel("Drag & Drop files here", self)
@@ -61,7 +83,19 @@ class DropEmpty(QtWidgets.QWidget):
widget.setAlignment(QtCore.Qt.AlignCenter)
widget.setAttribute(QtCore.Qt.WA_TranslucentBackground)
+ update_size_timer = QtCore.QTimer()
+ update_size_timer.setInterval(10)
+ update_size_timer.setSingleShot(True)
+
+ update_size_timer.timeout.connect(self._on_update_size_timer)
+
+ self._update_size_timer = update_size_timer
+
+ if extensions_label and not extensions_label.startswith(" "):
+ extensions_label = " " + extensions_label
+
self._single_item = single_item
+ self._extensions_label = extensions_label
self._allow_sequences = allow_sequences
self._allowed_extensions = set()
self._allow_folders = None
@@ -114,22 +148,51 @@ class DropEmpty(QtWidgets.QWidget):
items_label = "Single "
if len(allowed_items) == 1:
- allowed_items_label = allowed_items[0]
+ extensions_label = allowed_items[0]
elif len(allowed_items) == 2:
- allowed_items_label = " or ".join(allowed_items)
+ extensions_label = " or ".join(allowed_items)
else:
last_item = allowed_items.pop(-1)
new_last_item = " or ".join(last_item, allowed_items.pop(-1))
allowed_items.append(new_last_item)
- allowed_items_label = ", ".join(allowed_items)
+ extensions_label = ", ".join(allowed_items)
+
+ allowed_items_label = extensions_label
items_label += allowed_items_label
+ label_tooltip = None
if self._allowed_extensions:
items_label += " of\n{}".format(
", ".join(sorted(self._allowed_extensions))
)
+ if self._extensions_label:
+ label_tooltip = items_label
+ items_label = self._extensions_label
+
+ if self._items_label_widget.text() == items_label:
+ return
+
+ self._items_label_widget.setToolTip(label_tooltip)
self._items_label_widget.setText(items_label)
+ self._update_size_timer.start()
+
+ def resizeEvent(self, event):
+ super(DropEmpty, self).resizeEvent(event)
+ self._update_size_timer.start()
+
+ def _on_update_size_timer(self):
+ """Recalculate height of label with extensions.
+
+ Dynamic QLabel with word wrap does not handle properly it's sizeHint
+ calculations on show. This way it is recalculated. It is good practice
+ to trigger this method with small offset using '_update_size_timer'.
+ """
+
+ width = self._items_label_widget.width()
+ height = self._items_label_widget.heightForWidth(width)
+ self._items_label_widget.setMinimumHeight(height)
+ self._items_label_widget.updateGeometry()
def paintEvent(self, event):
super(DropEmpty, self).paintEvent(event)
@@ -162,6 +225,7 @@ class FilesModel(QtGui.QStandardItemModel):
def __init__(self, single_item, allow_sequences):
super(FilesModel, self).__init__()
+ self._id = str(uuid.uuid4())
self._single_item = single_item
self._multivalue = False
self._allow_sequences = allow_sequences
@@ -171,6 +235,10 @@ class FilesModel(QtGui.QStandardItemModel):
self._filenames_by_dirpath = collections.defaultdict(set)
self._items_by_dirpath = collections.defaultdict(list)
+ @property
+ def id(self):
+ return self._id
+
def set_multivalue(self, multivalue):
"""Disable filtering."""
@@ -245,6 +313,66 @@ class FilesModel(QtGui.QStandardItemModel):
return item_id, item
+ def mimeData(self, indexes):
+ item_ids = [
+ index.data(ITEM_ID_ROLE)
+ for index in indexes
+ ]
+
+ item_ids_data = convert_data_to_bytes(item_ids)
+ mime_data = super(FilesModel, self).mimeData(indexes)
+ mime_data.setData("files_widget/internal_move", item_ids_data)
+
+ file_items = []
+ for item_id in item_ids:
+ file_item = self.get_file_item_by_id(item_id)
+ if file_item:
+ file_items.append(file_item.to_dict())
+
+ full_item_data = convert_data_to_bytes({
+ "items": file_items,
+ "id": self._id
+ })
+ mime_data.setData("files_widget/full_data", full_item_data)
+ return mime_data
+
+ def dropMimeData(self, mime_data, action, row, col, index):
+ item_ids = convert_bytes_to_json(
+ mime_data.data("files_widget/internal_move")
+ )
+ if item_ids is None:
+ return False
+
+ # Find matching item after which will be items moved
+ # - store item before moved items are removed
+ root = self.invisibleRootItem()
+ if row >= 0:
+ src_item = self.item(row)
+ else:
+ src_item_id = index.data(ITEM_ID_ROLE)
+ src_item = self._items_by_id.get(src_item_id)
+
+ # Take out items that should be moved
+ items = []
+ for item_id in item_ids:
+ item = self._items_by_id.get(item_id)
+ if item:
+ self.takeRow(item.row())
+ items.append(item)
+
+ # Skip if there are not items that can be moved
+ if not items:
+ return False
+
+ # Calculate row where items should be inserted
+ if src_item:
+ src_row = src_item.row()
+ else:
+ src_row = root.rowCount()
+
+ root.insertRow(src_row, items)
+ return True
+
class FilesProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self, *args, **kwargs):
@@ -428,6 +556,9 @@ class FilesView(QtWidgets.QListView):
QtWidgets.QAbstractItemView.ExtendedSelection
)
self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
+ self.setAcceptDrops(True)
+ self.setDragEnabled(True)
+ self.setDragDropMode(self.InternalMove)
remove_btn = InViewButton(self)
pix_enabled = paint_image_with_color(
@@ -529,11 +660,13 @@ class FilesView(QtWidgets.QListView):
class FilesWidget(QtWidgets.QFrame):
value_changed = QtCore.Signal()
- def __init__(self, single_item, allow_sequences, parent):
+ def __init__(self, single_item, allow_sequences, extensions_label, parent):
super(FilesWidget, self).__init__(parent)
self.setAcceptDrops(True)
- empty_widget = DropEmpty(single_item, allow_sequences, self)
+ empty_widget = DropEmpty(
+ single_item, allow_sequences, extensions_label, self
+ )
files_model = FilesModel(single_item, allow_sequences)
files_proxy_model = FilesProxyModel()
@@ -553,6 +686,7 @@ class FilesWidget(QtWidgets.QFrame):
files_view.context_menu_requested.connect(
self._on_context_menu_requested
)
+
self._in_set_value = False
self._single_item = single_item
self._multivalue = False
@@ -637,8 +771,6 @@ class FilesWidget(QtWidgets.QFrame):
)
self._widgets_by_id[item_id] = widget
- self._files_proxy_model.sort(0)
-
if not self._in_set_value:
self.value_changed.emit()
@@ -743,12 +875,22 @@ class FilesWidget(QtWidgets.QFrame):
event.setDropAction(QtCore.Qt.CopyAction)
event.accept()
+ full_data_value = mime_data.data("files_widget/full_data")
+ if self._handle_full_data_drag(full_data_value):
+ event.setDropAction(QtCore.Qt.CopyAction)
+ event.accept()
+
def dragLeaveEvent(self, event):
event.accept()
def dropEvent(self, event):
+ if self._multivalue:
+ return
+
mime_data = event.mimeData()
- if not self._multivalue and mime_data.hasUrls():
+ if mime_data.hasUrls():
+ event.accept()
+ # event.setDropAction(QtCore.Qt.CopyAction)
filepaths = []
for url in mime_data.urls():
filepath = url.toLocalFile()
@@ -759,7 +901,58 @@ class FilesWidget(QtWidgets.QFrame):
filepaths = self._files_proxy_model.filter_valid_files(filepaths)
if filepaths:
self._add_filepaths(filepaths)
- event.accept()
+
+ if self._handle_full_data_drop(
+ mime_data.data("files_widget/full_data")
+ ):
+ event.setDropAction(QtCore.Qt.CopyAction)
+ event.accept()
+
+ super(FilesWidget, self).dropEvent(event)
+
+ def _handle_full_data_drag(self, value):
+ if value is None:
+ return False
+
+ full_data = convert_bytes_to_json(value)
+ if full_data is None:
+ return False
+
+ if full_data["id"] == self._files_model.id:
+ return False
+ return True
+
+ def _handle_full_data_drop(self, value):
+ if value is None:
+ return False
+
+ full_data = convert_bytes_to_json(value)
+ if full_data is None:
+ return False
+
+ if full_data["id"] == self._files_model.id:
+ return False
+
+ for item in full_data["items"]:
+ filepaths = [
+ os.path.join(item["directory"], filename)
+ for filename in item["filenames"]
+ ]
+ filepaths = self._files_proxy_model.filter_valid_files(filepaths)
+ if filepaths:
+ self._add_filepaths(filepaths)
+
+ if self._copy_modifiers_enabled():
+ return False
+ return True
+
+ def _copy_modifiers_enabled(self):
+ if (
+ QtWidgets.QApplication.keyboardModifiers()
+ & QtCore.Qt.ControlModifier
+ ):
+ return True
+ return False
def _add_filepaths(self, filepaths):
self._files_model.add_filepaths(filepaths)
diff --git a/openpype/widgets/attribute_defs/widgets.py b/openpype/widgets/attribute_defs/widgets.py
index b6493b80a8..975b2df955 100644
--- a/openpype/widgets/attribute_defs/widgets.py
+++ b/openpype/widgets/attribute_defs/widgets.py
@@ -443,7 +443,10 @@ class UnknownAttrWidget(_BaseAttrDefWidget):
class FileAttrWidget(_BaseAttrDefWidget):
def _ui_init(self):
input_widget = FilesWidget(
- self.attr_def.single_item, self.attr_def.allow_sequences, self
+ self.attr_def.single_item,
+ self.attr_def.allow_sequences,
+ self.attr_def.extensions_label,
+ self
)
if self.attr_def.tooltip:
diff --git a/pyproject.toml b/pyproject.toml
index f5bd7cc946..4bdaaab4ed 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "OpenPype"
-version = "3.12.1-nightly.4" # OpenPype
+version = "3.12.1" # OpenPype
description = "Open VFX and Animation pipeline with support."
authors = ["OpenPype Team "]
license = "MIT License"
diff --git a/tools/build.ps1 b/tools/build.ps1
index ff28544954..efb41e6c1b 100644
--- a/tools/build.ps1
+++ b/tools/build.ps1
@@ -28,6 +28,13 @@ if($arguments -eq "--no-submodule-update") {
$disable_submodule_update=$true
}
+$current_dir = Get-Location
+$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$openpype_root = (Get-Item $script_dir).parent.FullName
+
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell"
+
function Start-Progress {
param([ScriptBlock]$code)
$scroll = "/-\|/-\|"
@@ -72,14 +79,18 @@ function Exit-WithCode($exitcode) {
function Show-PSWarning() {
if ($PSVersionTable.PSVersion.Major -lt 7) {
- Write-Host "!!! " -NoNewline -ForegroundColor Red
- Write-Host "You are using old version of PowerShell. $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)"
- Write-Host "Please update to at least 7.0 - " -NoNewline -ForegroundColor Gray
- Write-Host "https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White
+ Write-Color -Text "!!! ", "You are using old version of PowerShell - ", "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" -Color Red, Yellow, White
+ Write-Color -Text " Please update to at least 7.0 - ", "https://github.com/PowerShell/PowerShell/releases" -Color Yellow, White
Exit-WithCode 1
}
}
+function Install-Poetry() {
+ Write-Color -Text ">>> ", "Installing Poetry ... " -Color Green, Gray
+ $env:POETRY_HOME="$openpype_root\.poetry"
+ (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/install-poetry.py -UseBasicParsing).Content | python -
+}
+
$art = @"
. . .. . ..
@@ -103,10 +114,6 @@ Write-Host $art -ForegroundColor DarkGreen
# Enable if PS 7.x is needed.
# Show-PSWarning
-$current_dir = Get-Location
-$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
-$openpype_root = (Get-Item $script_dir).parent.FullName
-
$env:_INSIDE_OPENPYPE_TOOL = "1"
if (-not (Test-Path 'env:POETRY_HOME')) {
@@ -119,8 +126,7 @@ $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py"
$result = [regex]::Matches($version_file, '__version__ = "(?\d+\.\d+.\d+.*)"')
$openpype_version = $result[0].Groups['version'].Value
if (-not $openpype_version) {
- Write-Host "!!! " -ForegroundColor yellow -NoNewline
- Write-Host "Cannot determine OpenPype version."
+ Write-Color -Text "!!! ", "Cannot determine OpenPype version." -Color Yellow, Gray
Exit-WithCode 1
}
@@ -129,75 +135,60 @@ if (-not (Test-Path -PathType Container -Path "$($openpype_root)\build")) {
New-Item -ItemType Directory -Force -Path "$($openpype_root)\build"
}
-Write-Host "--- " -NoNewline -ForegroundColor yellow
-Write-Host "Cleaning build directory ..."
+Write-Color -Text "--- ", "Cleaning build directory ..." -Color Yellow, Gray
try {
Remove-Item -Recurse -Force "$($openpype_root)\build\*"
}
catch {
- Write-Host "!!! " -NoNewline -ForegroundColor Red
- Write-Host "Cannot clean build directory, possibly because process is using it."
- Write-Host $_.Exception.Message
+ Write-Color -Text "!!! ", "Cannot clean build directory, possibly because process is using it." -Color Red, Gray
+ Write-Color -Text $_.Exception.Message -Color Red
Exit-WithCode 1
}
if (-not $disable_submodule_update) {
- Write-Host ">>> " -NoNewLine -ForegroundColor green
- Write-Host "Making sure submodules are up-to-date ..."
- git submodule update --init --recursive
+ Write-Color -Text ">>> ", "Making sure submodules are up-to-date ..." -Color Green, Gray
+ & git submodule update --init --recursive
} else {
- Write-Host "*** " -NoNewLine -ForegroundColor yellow
- Write-Host "Not updating submodules ..."
+ Write-Color -Text "*** ", "Not updating submodules ..." -Color Green, Gray
}
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "OpenPype [ " -NoNewline -ForegroundColor white
-Write-host $openpype_version -NoNewline -ForegroundColor green
-Write-Host " ]" -ForegroundColor white
+Write-Color -Text ">>> ", "OpenPype [ ", $openpype_version, " ]" -Color Green, White, Cyan, White
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
- Write-Host "*** " -NoNewline -ForegroundColor Yellow
- Write-Host "We need to install Poetry create virtual env first ..."
+ Write-Color -Text "NOT FOUND" -Color Yellow
+ Write-Color -Text "*** ", "We need to install Poetry create virtual env first ..." -Color Yellow, Gray
& "$openpype_root\tools\create_env.ps1"
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Cleaning cache files ... " -NoNewline
+Write-Color -Text ">>> ", "Cleaning cache files ... " -Color Green, Gray -NoNewline
Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force
Get-ChildItem $openpype_root -Filter "*.pyo" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force
Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force -Recurse
-Write-Host "OK" -ForegroundColor green
+Write-Color -Text "OK" -Color green
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Building OpenPype ..."
+Write-Color -Text ">>> ", "Building OpenPype ..." -Color Green, White
$startTime = [int][double]::Parse((Get-Date -UFormat %s))
$out = & "$($env:POETRY_HOME)\bin\poetry" run python setup.py build 2>&1
Set-Content -Path "$($openpype_root)\build\build.log" -Value $out
if ($LASTEXITCODE -ne 0)
{
- Write-Host "------------------------------------------" -ForegroundColor Red
+ Write-Color -Text "------------------------------------------" -Color Red
Get-Content "$($openpype_root)\build\build.log"
- Write-Host "------------------------------------------" -ForegroundColor Red
- Write-Host "!!! " -NoNewLine -ForegroundColor Red
- Write-Host "Build failed. Check the log: " -NoNewline
- Write-Host ".\build\build.log" -ForegroundColor Yellow
+ Write-Color -Text "------------------------------------------" -Color Yellow
+ Write-Color -Text "!!! ", "Build failed. Check the log: ", ".\build\build.log" -Color Red, Yellow, White
Exit-WithCode $LASTEXITCODE
}
Set-Content -Path "$($openpype_root)\build\build.log" -Value $out
& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\tools\build_dependencies.py"
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "restoring current directory"
+Write-Color -Text ">>> ", "Restoring current directory" -Color Green, Gray
Set-Location -Path $current_dir
$endTime = [int][double]::Parse((Get-Date -UFormat %s))
-Write-Host "*** " -NoNewline -ForegroundColor Cyan
-Write-Host "All done in $($endTime - $startTime) secs. You will find OpenPype and build log in " -NoNewLine
-Write-Host "'.\build'" -NoNewline -ForegroundColor Green
-Write-Host " directory."
+New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype build complete!", "All done in $($endTime - $startTime) secs. You will find OpenPype and build log in build directory."
+
+Write-Color -Text "*** ", "All done in ", $($endTime - $startTime), " secs. You will find OpenPype and build log in ", "'.\build'", " directory." -Color Green, Gray, White, Gray, White, Gray
diff --git a/tools/build_win_installer.ps1 b/tools/build_win_installer.ps1
index 49fa803742..8024a5a3b2 100644
--- a/tools/build_win_installer.ps1
+++ b/tools/build_win_installer.ps1
@@ -11,6 +11,12 @@
PS> .\build_win_installer.ps1
#>
+$current_dir = Get-Location
+$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$openpype_root = (Get-Item $script_dir).parent.FullName
+
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell"
function Start-Progress {
param([ScriptBlock]$code)
@@ -44,7 +50,6 @@ function Start-Progress {
#>
}
-
function Exit-WithCode($exitcode) {
# Only exit this host process if it's a child of another PowerShell parent process...
$parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId
@@ -56,10 +61,8 @@ function Exit-WithCode($exitcode) {
function Show-PSWarning() {
if ($PSVersionTable.PSVersion.Major -lt 7) {
- Write-Host "!!! " -NoNewline -ForegroundColor Red
- Write-Host "You are using old version of PowerShell. $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)"
- Write-Host "Please update to at least 7.0 - " -NoNewline -ForegroundColor Gray
- Write-Host "https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White
+ Write-Color -Text "!!! ", "You are using old version of PowerShell - ", "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" -Color Red, Yellow, White
+ Write-Color -Text " Please update to at least 7.0 - ", "https://github.com/PowerShell/PowerShell/releases" -Color Yellow, White
Exit-WithCode 1
}
}
@@ -87,9 +90,6 @@ Write-Host $art -ForegroundColor DarkGreen
# Enable if PS 7.x is needed.
# Show-PSWarning
-$current_dir = Get-Location
-$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
-$openpype_root = (Get-Item $script_dir).parent.FullName
Set-Location -Path $openpype_root
@@ -97,16 +97,15 @@ $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py"
$result = [regex]::Matches($version_file, '__version__ = "(?\d+\.\d+.\d+.*)"')
$openpype_version = $result[0].Groups['version'].Value
if (-not $openpype_version) {
- Write-Host "!!! " -ForegroundColor yellow -NoNewline
- Write-Host "Cannot determine OpenPype version."
+ Write-Color -Text "!!! ", "Cannot determine OpenPype version." -Color Yellow, Gray
Exit-WithCode 1
}
+
$env:BUILD_VERSION = $openpype_version
iscc
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Detecting host Python ... " -NoNewline
+Write-Color ">>> ", "Detecting host Python ... " -Color Green, White -NoNewline
$python = "python"
if (Get-Command "pyenv" -ErrorAction SilentlyContinue) {
$pyenv_python = & pyenv which python
@@ -115,7 +114,7 @@ if (Get-Command "pyenv" -ErrorAction SilentlyContinue) {
}
}
if (-not (Get-Command $python -ErrorAction SilentlyContinue)) {
- Write-Host "!!! Python not detected" -ForegroundColor red
+ Write-Color "!!! ", "Python not detected" -Color Red, Yellow
Set-Location -Path $current_dir
Exit-WithCode 1
}
@@ -128,7 +127,7 @@ $p = & $python -c $version_command
$env:PYTHON_VERSION = $p
$m = $p -match '(\d+)\.(\d+)'
if(-not $m) {
- Write-Host "!!! Cannot determine version" -ForegroundColor red
+ Write-Color "!!! ", "Cannot determine version" -Color Red, Yellow
Set-Location -Path $current_dir
Exit-WithCode 1
}
@@ -145,8 +144,7 @@ if (($matches[1] -lt 3) -or ($matches[2] -lt 7)) {
Write-Host "OK [ $p ]" -ForegroundColor green
}
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Creating OpenPype installer ... " -ForegroundColor white
+Write-Color -Text ">>> ", "Creating OpenPype installer ... " -Color Green, White
$build_dir_command = @"
import sys
@@ -155,24 +153,25 @@ print('exe.{}-{}'.format(get_platform(), sys.version[0:3]))
"@
$build_dir = & $python -c $build_dir_command
-Write-Host "Build directory ... ${build_dir}" -ForegroundColor white
+Write-Color -Text "--- ", "Build directory ", "${build_dir}" -Color Green, Gray, White
$env:BUILD_DIR = $build_dir
-if (Get-Command iscc -errorAction SilentlyContinue -ErrorVariable ProcessError)
-{
- iscc "$openpype_root\inno_setup.iss"
-}else {
- Write-Host "!!! Cannot find Inno Setup command" -ForegroundColor red
- Write-Host "!!! You can download it at https://jrsoftware.org/" -ForegroundColor red
+if (-not (Get-Command iscc -errorAction SilentlyContinue -ErrorVariable ProcessError)) {
+ Write-Color -Text "!!! ", "Cannot find Inno Setup command" -Color Red, Yellow
+ Write-Color "!!! You can download it at https://jrsoftware.org/" -ForegroundColor red
Exit-WithCode 1
}
+& iscc "$openpype_root\inno_setup.iss"
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "restoring current directory"
+if ($LASTEXITCODE -ne 0) {
+ Write-Color -Text "!!! ", "Creating installer failed." -Color Red, Yellow
+ Exit-WithCode 1
+}
+
+Write-Color -Text ">>> ", "Restoring current directory" -Color Green, Gray
Set-Location -Path $current_dir
-Write-Host "*** " -NoNewline -ForegroundColor Cyan
-Write-Host "All done. You will find OpenPype installer in " -NoNewLine
-Write-Host "'.\build'" -NoNewline -ForegroundColor Green
-Write-Host " directory."
+New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype build complete!", "All done. You will find You will find OpenPype installer in '.\build' directory."
+
+Write-Color -Text "*** ", "All done. You will find OpenPype installer in ", "'.\build'", " directory." -Color Green, Gray, White, Gray
diff --git a/tools/create_env.ps1 b/tools/create_env.ps1
index c307ba2031..c0cbe9775b 100644
--- a/tools/create_env.ps1
+++ b/tools/create_env.ps1
@@ -24,6 +24,15 @@ if($arguments -eq "--verbose") {
$poetry_verbosity="-vvv"
}
+$current_dir = Get-Location
+$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$openpype_root = (Get-Item $script_dir).parent.FullName
+
+& git submodule update --init --recursive
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell"
+
+
function Exit-WithCode($exitcode) {
# Only exit this host process if it's a child of another PowerShell parent process...
$parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId
@@ -36,30 +45,26 @@ function Exit-WithCode($exitcode) {
function Show-PSWarning() {
if ($PSVersionTable.PSVersion.Major -lt 7) {
- Write-Host "!!! " -NoNewline -ForegroundColor Red
- Write-Host "You are using old version of PowerShell. $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)"
- Write-Host "Please update to at least 7.0 - " -NoNewline -ForegroundColor Gray
- Write-Host "https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White
+ Write-Color -Text "!!! ", "You are using old version of PowerShell - ", "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" -Color Red, Yellow, White
+ Write-Color -Text " Please update to at least 7.0 - ", "https://github.com/PowerShell/PowerShell/releases" -Color Yellow, White
Exit-WithCode 1
}
}
function Install-Poetry() {
- Write-Host ">>> " -NoNewline -ForegroundColor Green
- Write-Host "Installing Poetry ... "
+ Write-Color -Text ">>> ", "Installing Poetry ... " -Color Green, Gray
$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."
+ Write-Color -Text "!!! ", "Using pyenv but having no local or global version of Python set." -Color Red, Yellow
Exit-WithCode 1
}
}
$python = & pyenv which python
-
+
}
$env:POETRY_HOME="$openpype_root\.poetry"
@@ -68,8 +73,7 @@ function Install-Poetry() {
function Test-Python() {
- Write-Host ">>> " -NoNewline -ForegroundColor green
- Write-Host "Detecting host Python ... " -NoNewline
+ Write-Color -Text ">>> ", "Detecting host Python ... " -Color Green, Gray -NoNewline
$python = "python"
if (Get-Command "pyenv" -ErrorAction SilentlyContinue) {
$pyenv_python = & pyenv which python
@@ -97,22 +101,17 @@ print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))
}
# We are supporting python 3.7 only
if (($matches[1] -lt 3) -or ($matches[2] -lt 7)) {
- Write-Host "FAILED Version [ $p ] is old and unsupported" -ForegroundColor red
+ Write-Color -Text "FAILED ", "Version ", "[", $p ,"]", "is old and unsupported" -Color Red, Yellow, Cyan, White, Cyan, Yellow
Set-Location -Path $current_dir
Exit-WithCode 1
} elseif (($matches[1] -eq 3) -and ($matches[2] -gt 7)) {
- Write-Host "WARNING Version [ $p ] is unsupported, use at your own risk." -ForegroundColor yellow
- Write-Host "*** " -NoNewline -ForegroundColor yellow
- Write-Host "OpenPype supports only Python 3.7" -ForegroundColor white
+ Write-Color -Text "WARNING Version ", "[", $p, "]", " is unsupported, use at your own risk." -Color Yellow, Cyan, White, Cyan, Yellow
+ Write-Color -Text "*** ", "OpenPype supports only Python 3.7" -Color Yellow, White
} else {
- Write-Host "OK [ $p ]" -ForegroundColor green
+ Write-Color "OK ", "[", $p, "]" -Color Green, Cyan, White, Cyan
}
}
-$current_dir = Get-Location
-$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
-$openpype_root = (Get-Item $script_dir).parent.FullName
-
if (-not (Test-Path 'env:POETRY_HOME')) {
$env:POETRY_HOME = "$openpype_root\.poetry"
}
@@ -150,41 +149,38 @@ $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py"
$result = [regex]::Matches($version_file, '__version__ = "(?\d+\.\d+.\d+.*)"')
$openpype_version = $result[0].Groups['version'].Value
if (-not $openpype_version) {
- Write-Host "!!! " -ForegroundColor yellow -NoNewline
- Write-Host "Cannot determine OpenPype version."
+ Write-Color -Text "!!! ", "Cannot determine OpenPype version." -Color Red, Yellow
Set-Location -Path $current_dir
Exit-WithCode 1
}
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Found OpenPype version " -NoNewline
-Write-Host "[ $($openpype_version) ]" -ForegroundColor Green
+Write-Color -Text ">>> ", "Found OpenPype version ", "[ ", $($openpype_version), " ]" -Color Green, Gray, Cyan, White, Cyan
Test-Python
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
+ Write-Color -Text "NOT FOUND" -Color Yellow
Install-Poetry
- Write-Host "INSTALLED" -ForegroundColor Cyan
+ Write-Color -Text "INSTALLED" -Color Cyan
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
if (-not (Test-Path -PathType Leaf -Path "$($openpype_root)\poetry.lock")) {
- Write-Host ">>> " -NoNewline -ForegroundColor green
- Write-Host "Installing virtual environment and creating lock."
+ Write-Color -Text ">>> ", "Installing virtual environment and creating lock." -Color Green, Gray
} else {
- Write-Host ">>> " -NoNewline -ForegroundColor green
- Write-Host "Installing virtual environment from lock."
+ Write-Color -Text ">>> ", "Installing virtual environment from lock." -Color Green, Gray
}
+$startTime = [int][double]::Parse((Get-Date -UFormat %s))
& "$env:POETRY_HOME\bin\poetry" install --no-root $poetry_verbosity --ansi
if ($LASTEXITCODE -ne 0) {
- Write-Host "!!! " -ForegroundColor yellow -NoNewline
- Write-Host "Poetry command failed."
+ Write-Color -Text "!!! ", "Poetry command failed." -Color Red, Yellow
Set-Location -Path $current_dir
Exit-WithCode 1
}
+$endTime = [int][double]::Parse((Get-Date -UFormat %s))
Set-Location -Path $current_dir
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Virtual environment created."
+
+New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype", "Virtual environment created.", "All done in $($endTime - $startTime) secs."
+
+Write-Color -Text ">>> ", "Virtual environment created." -Color Green, White
diff --git a/tools/create_zip.ps1 b/tools/create_zip.ps1
index e33445d1fa..b4b66424ca 100644
--- a/tools/create_zip.ps1
+++ b/tools/create_zip.ps1
@@ -19,6 +19,13 @@ PS> .\create_zip.ps1 --path C:\OpenPype
#>
+$current_dir = Get-Location
+$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$openpype_root = (Get-Item $script_dir).parent.FullName
+
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell"
+
function Exit-WithCode($exitcode) {
# Only exit this host process if it's a child of another PowerShell parent process...
$parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId
@@ -31,18 +38,12 @@ function Exit-WithCode($exitcode) {
function Show-PSWarning() {
if ($PSVersionTable.PSVersion.Major -lt 7) {
- Write-Host "!!! " -NoNewline -ForegroundColor Red
- Write-Host "You are using old version of PowerShell. $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)"
- Write-Host "Please update to at least 7.0 - " -NoNewline -ForegroundColor Gray
- Write-Host "https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White
+ Write-Color -Text "!!! ", "You are using old version of PowerShell - ", "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" -Color Red, Yellow, White
+ Write-Color -Text " Please update to at least 7.0 - ", "https://github.com/PowerShell/PowerShell/releases" -Color Yellow, White
Exit-WithCode 1
}
}
-$current_dir = Get-Location
-$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
-$openpype_root = (Get-Item $script_dir).parent.FullName
-
$env:_INSIDE_OPENPYPE_TOOL = "1"
if (-not (Test-Path 'env:POETRY_HOME')) {
@@ -78,31 +79,25 @@ $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py"
$result = [regex]::Matches($version_file, '__version__ = "(?\d+\.\d+.\d+.*)"')
$openpype_version = $result[0].Groups['version'].Value
if (-not $openpype_version) {
- Write-Host "!!! " -ForegroundColor yellow -NoNewline
- Write-Host "Cannot determine OpenPype version."
+ Write-Color -Text "!!! ", "Cannot determine OpenPype version." -Color Yellow, Gray
Exit-WithCode 1
}
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
- Write-Host "*** " -NoNewline -ForegroundColor Yellow
- Write-Host "We need to install Poetry create virtual env first ..."
+ Write-Color -Text "NOT FOUND" -Color Yellow
+ Write-Color -Text "*** ", "We need to install Poetry create virtual env first ..." -Color Yellow, Gray
& "$openpype_root\tools\create_env.ps1"
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Cleaning cache files ... " -NoNewline
+Write-Color -Text ">>> ", "Cleaning cache files ... " -Color Green, Gray -NoNewline
Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse| Where-Object {( $_.FullName -inotmatch '\\build\\' ) -and ( $_.FullName -inotmatch '\\.venv' )} | Remove-Item -Force -Recurse
Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Where-Object {( $_.FullName -inotmatch '\\build\\' ) -and ( $_.FullName -inotmatch '\\.venv' )} | Remove-Item -Force
Get-ChildItem $openpype_root -Filter "*.pyo" -Force -Recurse | Where-Object {( $_.FullName -inotmatch '\\build\\' ) -and ( $_.FullName -inotmatch '\\.venv' )} | Remove-Item -Force
-Write-Host "OK" -ForegroundColor green
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Generating zip from current sources ..."
+Write-Color -Text ">>> ", "Generating zip from current sources ..." -Color Green, Gray
$env:PYTHONPATH="$($openpype_root);$($env:PYTHONPATH)"
$env:OPENPYPE_ROOT="$($openpype_root)"
& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\tools\create_zip.py" $ARGS
diff --git a/tools/fetch_thirdparty_libs.ps1 b/tools/fetch_thirdparty_libs.ps1
index 16f7b70e7a..41a3585ff9 100644
--- a/tools/fetch_thirdparty_libs.ps1
+++ b/tools/fetch_thirdparty_libs.ps1
@@ -15,6 +15,9 @@ $current_dir = Get-Location
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$openpype_root = (Get-Item $script_dir).parent.FullName
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell"
+
$env:_INSIDE_OPENPYPE_TOOL = "1"
if (-not (Test-Path 'env:POETRY_HOME')) {
@@ -23,16 +26,16 @@ if (-not (Test-Path 'env:POETRY_HOME')) {
Set-Location -Path $openpype_root
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
- Write-Host "*** " -NoNewline -ForegroundColor Yellow
- Write-Host "We need to install Poetry create virtual env first ..."
+ Write-Color -Text "NOT FOUND" -Color Yellow
+ Write-Color -Text "*** ", "We need to install Poetry create virtual env first ..." -Color Yellow, Gray
& "$openpype_root\tools\create_env.ps1"
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
-
+$startTime = [int][double]::Parse((Get-Date -UFormat %s))
& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\tools\fetch_thirdparty_libs.py"
+$endTime = [int][double]::Parse((Get-Date -UFormat %s))
Set-Location -Path $current_dir
+New-BurntToastNotification -AppLogo "$openpype_root/openpype/resources/icons/openpype_icon.png" -Text "OpenPype", "Dependencies downloaded", "All done in $($endTime - $startTime) secs."
diff --git a/tools/make_docs.ps1 b/tools/make_docs.ps1
index 45a11171ae..d356f081de 100644
--- a/tools/make_docs.ps1
+++ b/tools/make_docs.ps1
@@ -44,27 +44,30 @@ $art = @"
"@
+$current_dir = Get-Location
+$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$openpype_root = (Get-Item $script_dir).parent.FullName
+
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell"
+
Write-Host $art -ForegroundColor DarkGreen
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
- Write-Host "*** " -NoNewline -ForegroundColor Yellow
- Write-Host "We need to install Poetry create virtual env first ..."
- & "$openpype_root\tools\create_env.ps1"
+ Write-Color -Text "NOT FOUND" -Color Yellow
+ Install-Poetry
+ Write-Color -Text "INSTALLED" -Color Cyan
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
-Write-Host "This will not overwrite existing source rst files, only scan and add new."
+Write-Color -Text "... ", "This will not overwrite existing source rst files, only scan and add new." -Color Yellow, Gray
Set-Location -Path $openpype_root
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Running apidoc ..."
+Write-Color -Text ">>> ", "Running apidoc ..." -Color Green, Gray
& "$env:POETRY_HOME\bin\poetry" run sphinx-apidoc -M -e -d 10 --ext-intersphinx --ext-todo --ext-coverage --ext-viewcode -o "$($openpype_root)\docs\source" igniter
& "$env:POETRY_HOME\bin\poetry" run sphinx-apidoc.exe -M -e -d 10 --ext-intersphinx --ext-todo --ext-coverage --ext-viewcode -o "$($openpype_root)\docs\source" openpype vendor, openpype\vendor
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Building html ..."
+Write-Color -Text ">>> ", "Building html ..." -Color Green, Gray
& "$env:POETRY_HOME\bin\poetry" run python "$($openpype_root)\setup.py" build_sphinx
Set-Location -Path $current_dir
diff --git a/tools/run_mongo.ps1 b/tools/run_mongo.ps1
index f6fa37207d..934ce67181 100644
--- a/tools/run_mongo.ps1
+++ b/tools/run_mongo.ps1
@@ -11,6 +11,13 @@ PS> .\run_mongo.ps1
#>
+$current_dir = Get-Location
+$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$openpype_root = (Get-Item $script_dir).parent.FullName
+
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell"
+
$art = @"
. . .. . ..
@@ -43,8 +50,7 @@ function Exit-WithCode($exitcode) {
function Find-Mongo ($preferred_version) {
$defaultPath = "C:\Program Files\MongoDB\Server"
- Write-Host ">>> " -NoNewLine -ForegroundColor Green
- Write-Host "Detecting MongoDB ... " -NoNewline
+ Write-Color -Text ">>> ", "Detecting MongoDB ... " -Color Geen, Gray -NoNewline
if (-not (Get-Command "mongod" -ErrorAction SilentlyContinue)) {
if(Test-Path "$($defaultPath)\*\bin\mongod.exe" -PathType Leaf) {
# we have mongo server installed on standard Windows location
@@ -52,17 +58,14 @@ function Find-Mongo ($preferred_version) {
# $preferred_version.
$mongoVersions = Get-ChildItem -Directory 'C:\Program Files\MongoDB\Server' | Sort-Object -Property {$_.Name -as [int]}
if(Test-Path "$($mongoVersions[-1])\bin\mongod.exe" -PathType Leaf) {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
$use_version = $mongoVersions[-1]
foreach ($v in $mongoVersions) {
- Write-Host " - found [ " -NoNewline
- Write-Host $v -NoNewLine -ForegroundColor Cyan
- Write-Host " ]" -NoNewLine
-
+ Write-Color -Text " - found [ ", $v, " ]" - Color Cyan, White, Cyan -NoNewLine
$version = Split-Path $v -Leaf
if ($preferred_version -eq $version) {
- Write-Host " *" -ForegroundColor Green
+ Write-Color -Text " *" -Color Green
$use_version = $v
} else {
Write-Host ""
@@ -71,27 +74,20 @@ function Find-Mongo ($preferred_version) {
$env:PATH = "$($env:PATH);$($use_version)\bin\"
- Write-Host " - auto-added from [ " -NoNewline
- Write-Host "$($use_version)\bin\mongod.exe" -NoNewLine -ForegroundColor Cyan
- Write-Host " ]"
+ Write-Color -Text " - auto-added from [ ", "$($use_version)\bin\mongod.exe", " ]" -Color Cyan, White, Cyan
return "$($use_version)\bin\mongod.exe"
} else {
- Write-Host "FAILED " -NoNewLine -ForegroundColor Red
- Write-Host "MongoDB not detected" -ForegroundColor Yellow
- Write-Host "Tried to find it on standard location " -NoNewline -ForegroundColor Gray
- Write-Host " [ " -NoNewline -ForegroundColor Cyan
- Write-Host "$($mongoVersions[-1])\bin\mongod.exe" -NoNewline -ForegroundColor White
- Write-Host " ] " -NoNewLine -ForegroundColor Cyan
- Write-Host "but failed." -ForegroundColor Gray
+ Write-Color -Text "FAILED " -Color Red -NoNewLine
+ Write-Color -Text "MongoDB not detected" -Color Yellow
+ Write-Color -Text "Tried to find it on standard location ", "[ ", "$($mongoVersions[-1])\bin\mongod.exe", " ]", " but failed." -Color Gray, Cyan, White, Cyan, Gray -NoNewline
Exit-WithCode 1
}
} else {
- Write-Host "FAILED " -NoNewLine -ForegroundColor Red
- Write-Host "MongoDB not detected in PATH" -ForegroundColor Yellow
+ Write-Color -Text "FAILED ", "MongoDB not detected in PATH" -Color Red, Yellow
Exit-WithCode 1
}
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
return Get-Command "mongod" -ErrorAction SilentlyContinue
}
<#
@@ -104,9 +100,6 @@ function Find-Mongo ($preferred_version) {
#>
}
-$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
-$openpype_root = (Get-Item $script_dir).parent.FullName
-
# mongodb port
$port = 2707
@@ -116,15 +109,7 @@ $dbpath = (Get-Item $openpype_root).parent.FullName + "\mongo_db_data"
$preferred_version = "5.0"
$mongoPath = Find-Mongo $preferred_version
-Write-Host ">>> " -NoNewLine -ForegroundColor Green
-Write-Host "Using DB path: " -NoNewLine
-Write-Host " [ " -NoNewline -ForegroundColor Cyan
-Write-Host "$($dbpath)" -NoNewline -ForegroundColor White
-Write-Host " ] "-ForegroundColor Cyan
-Write-Host ">>> " -NoNewLine -ForegroundColor Green
-Write-Host "Port: " -NoNewLine
-Write-Host " [ " -NoNewline -ForegroundColor Cyan
-Write-Host "$($port)" -NoNewline -ForegroundColor White
-Write-Host " ] " -ForegroundColor Cyan
-Start-Process -FilePath $mongopath "--dbpath $($dbpath) --port $($port)" -PassThru | Out-Null
+Write-Color -Text ">>> ", "Using DB path: ", "[ ", "$($dbpath)", " ]" -Color Green, Gray, Cyan, White, Cyan
+Write-Color -Text ">>> ", "Port: ", "[ ", "$($port)", " ]", -Color Green, Gray, Cyan, White, Cyan
+Start-Process -FilePath $mongopath "--dbpath $($dbpath) --port $($port)" -PassThru | Out-Null
diff --git a/tools/run_project_manager.ps1 b/tools/run_project_manager.ps1
index a9cfbb1e7b..2932358c2a 100644
--- a/tools/run_project_manager.ps1
+++ b/tools/run_project_manager.ps1
@@ -35,6 +35,9 @@ $current_dir = Get-Location
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$openpype_root = (Get-Item $script_dir).parent.FullName
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell"
+
$env:_INSIDE_OPENPYPE_TOOL = "1"
# make sure Poetry is in PATH
@@ -45,15 +48,13 @@ $env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
Set-Location -Path $openpype_root
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
- Write-Host "*** " -NoNewline -ForegroundColor Yellow
- Write-Host "We need to install Poetry create virtual env first ..."
- & "$openpype_root\tools\create_env.ps1"
+ Write-Color -Text "NOT FOUND" -Color Yellow
+ Install-Poetry
+ Write-Color -Text "INSTALLED" -Color Cyan
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
& "$env:POETRY_HOME\bin\poetry" run python "$($openpype_root)\start.py" projectmanager
diff --git a/tools/run_settings.ps1 b/tools/run_settings.ps1
index 1c0aa6e8f3..918ea367ab 100644
--- a/tools/run_settings.ps1
+++ b/tools/run_settings.ps1
@@ -15,6 +15,9 @@ $current_dir = Get-Location
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$openpype_root = (Get-Item $script_dir).parent.FullName
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell"
+
$env:_INSIDE_OPENPYPE_TOOL = "1"
# make sure Poetry is in PATH
@@ -25,15 +28,13 @@ $env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
Set-Location -Path $openpype_root
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
- Write-Host "*** " -NoNewline -ForegroundColor Yellow
- Write-Host "We need to install Poetry create virtual env first ..."
- & "$openpype_root\tools\create_env.ps1"
+ Write-Color -Text "NOT FOUND" -Color Yellow
+ Install-Poetry
+ Write-Color -Text "INSTALLED" -Color Cyan
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
& "$env:POETRY_HOME\bin\poetry" run python "$($openpype_root)\start.py" settings --dev
diff --git a/tools/run_tests.ps1 b/tools/run_tests.ps1
index e631cb72df..7995c6a8e9 100644
--- a/tools/run_tests.ps1
+++ b/tools/run_tests.ps1
@@ -11,6 +11,13 @@ PS> .\run_test.ps1
#>
+$current_dir = Get-Location
+$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
+$openpype_root = (Get-Item $script_dir).parent.FullName
+
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell"
+
function Exit-WithCode($exitcode) {
# Only exit this host process if it's a child of another PowerShell parent process...
$parentPID = (Get-CimInstance -ClassName Win32_Process -Filter "ProcessId=$PID" | Select-Object -Property ParentProcessId).ParentProcessId
@@ -22,10 +29,8 @@ function Exit-WithCode($exitcode) {
function Show-PSWarning() {
if ($PSVersionTable.PSVersion.Major -lt 7) {
- Write-Host "!!! " -NoNewline -ForegroundColor Red
- Write-Host "You are using old version of PowerShell. $($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)"
- Write-Host "Please update to at least 7.0 - " -NoNewline -ForegroundColor Gray
- Write-Host "https://github.com/PowerShell/PowerShell/releases" -ForegroundColor White
+ Write-Color -Text "!!! ", "You are using old version of PowerShell - ", "$($PSVersionTable.PSVersion.Major).$($PSVersionTable.PSVersion.Minor)" -Color Red, Yellow, White
+ Write-Color -Text " Please update to at least 7.0 - ", "https://github.com/PowerShell/PowerShell/releases" -Color Yellow, White
Exit-WithCode 1
}
}
@@ -53,10 +58,6 @@ Write-Host $art -ForegroundColor DarkGreen
# Enable if PS 7.x is needed.
# Show-PSWarning
-$current_dir = Get-Location
-$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
-$openpype_root = (Get-Item $script_dir).parent.FullName
-
$env:_INSIDE_OPENPYPE_TOOL = "1"
if (-not (Test-Path 'env:POETRY_HOME')) {
@@ -69,46 +70,32 @@ $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py"
$result = [regex]::Matches($version_file, '__version__ = "(?\d+\.\d+.\d+.*)"')
$openpype_version = $result[0].Groups['version'].Value
if (-not $openpype_version) {
- Write-Host "!!! " -ForegroundColor yellow -NoNewline
- Write-Host "Cannot determine OpenPype version."
+ Write-Color -Text "!!! ", "Cannot determine OpenPype version." -Color Yellow, Gray
Exit-WithCode 1
}
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "OpenPype [ " -NoNewline -ForegroundColor white
-Write-host $openpype_version -NoNewline -ForegroundColor green
-Write-Host " ] ..." -ForegroundColor white
+Write-Color -Text ">>> ", "OpenPype [ ", $openpype_version, " ]" -Color Green, White, Cyan, White
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
- Write-Host "*** " -NoNewline -ForegroundColor Yellow
- Write-Host "We need to install Poetry create virtual env first ..."
+ Write-Color -Text "NOT FOUND" -Color Yellow
+ Write-Color -Text "*** ", "We need to install Poetry create virtual env first ..." -Color Yellow, Gray
& "$openpype_root\tools\create_env.ps1"
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Cleaning cache files ... " -NoNewline
+Write-Color -Text ">>> ", "Cleaning cache files ... " -Color Green, Gray -NoNewline
Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force
+Get-ChildItem $openpype_root -Filter "*.pyo" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force
Get-ChildItem $openpype_root -Filter "__pycache__" -Force -Recurse | Where-Object { $_.FullName -inotmatch 'build' } | Remove-Item -Force -Recurse
-Write-Host "OK" -ForegroundColor green
+Write-Color -Text "OK" -Color green
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "Testing OpenPype ..."
+Write-Color -Text ">>> ", "Testing OpenPype ..." -Color Green, White
$original_pythonpath = $env:PYTHONPATH
$env:PYTHONPATH="$($openpype_root);$($env:PYTHONPATH)"
& "$env:POETRY_HOME\bin\poetry" run pytest -x --capture=sys --print -W ignore::DeprecationWarning "$($openpype_root)/tests"
$env:PYTHONPATH = $original_pythonpath
-Write-Host ">>> " -NoNewline -ForegroundColor green
-Write-Host "restoring current directory"
+Write-Color -Text ">>> ", "Restoring current directory" -Color Green, Gray
Set-Location -Path $current_dir
-
-
-
-
-
-
diff --git a/tools/run_tray.ps1 b/tools/run_tray.ps1
index 872c1524a6..7dee3d0064 100644
--- a/tools/run_tray.ps1
+++ b/tools/run_tray.ps1
@@ -14,6 +14,9 @@ $current_dir = Get-Location
$script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent
$openpype_root = (Get-Item $script_dir).parent.FullName
+# Install PSWriteColor to support colorized output to terminal
+$env:PSModulePath = $env:PSModulePath + ";$($openpype_root)\vendor\powershell"
+
$env:_INSIDE_OPENPYPE_TOOL = "1"
# make sure Poetry is in PATH
@@ -24,15 +27,13 @@ $env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin"
Set-Location -Path $openpype_root
-Write-Host ">>> " -NoNewline -ForegroundColor Green
-Write-Host "Reading Poetry ... " -NoNewline
+Write-Color -Text ">>> ", "Reading Poetry ... " -Color Green, Gray -NoNewline
if (-not (Test-Path -PathType Container -Path "$($env:POETRY_HOME)\bin")) {
- Write-Host "NOT FOUND" -ForegroundColor Yellow
- Write-Host "*** " -NoNewline -ForegroundColor Yellow
- Write-Host "We need to install Poetry create virtual env first ..."
+ Write-Color -Text "NOT FOUND" -Color Yellow
+ Write-Color -Text "*** ", "We need to install Poetry create virtual env first ..." -Color Yellow, Gray
& "$openpype_root\tools\create_env.ps1"
} else {
- Write-Host "OK" -ForegroundColor Green
+ Write-Color -Text "OK" -Color Green
}
& "$($env:POETRY_HOME)\bin\poetry" run python "$($openpype_root)\start.py" tray --debug
diff --git a/vendor/powershell/BurntToast b/vendor/powershell/BurntToast
new file mode 160000
index 0000000000..ae0acdd870
--- /dev/null
+++ b/vendor/powershell/BurntToast
@@ -0,0 +1 @@
+Subproject commit ae0acdd870a2fd8d9f0d147de22dc36d6c5e399e
diff --git a/vendor/powershell/PSWriteColor b/vendor/powershell/PSWriteColor
new file mode 160000
index 0000000000..12eda384eb
--- /dev/null
+++ b/vendor/powershell/PSWriteColor
@@ -0,0 +1 @@
+Subproject commit 12eda384ebd7a7954e15855e312215c009c97114
diff --git a/openpype/hosts/unreal/integration/UE_5.0/Content/Python/__init__.py b/vendor/powershell/README.md
similarity index 100%
rename from openpype/hosts/unreal/integration/UE_5.0/Content/Python/__init__.py
rename to vendor/powershell/README.md