diff --git a/.gitignore b/.gitignore index ebb47e55d2..26bf7cf65f 100644 --- a/.gitignore +++ b/.gitignore @@ -91,4 +91,9 @@ website/i18n/* website/debug.log -website/.docusaurus \ No newline at end of file +website/.docusaurus + +# Poetry +######## + +.poetry/ \ No newline at end of file diff --git a/igniter/install_dialog.py b/igniter/install_dialog.py index 411fac1e96..1ec8cc6768 100644 --- a/igniter/install_dialog.py +++ b/igniter/install_dialog.py @@ -383,8 +383,6 @@ class InstallDialog(QtWidgets.QDialog): else: raise AssertionError("BUG: Unknown variant \"{}\"".format(option)) - self._enable_buttons() - def _run_openpype_from_code(self): os.environ["OPENPYPE_MONGO"] = self.mongo_url try: @@ -423,6 +421,7 @@ class InstallDialog(QtWidgets.QDialog): QtWidgets.QApplication.processEvents() self.done(3) else: + self._enable_buttons() self._show_console() def _update_progress(self, progress: int): diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index a83ff98c99..909993a173 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -1124,16 +1124,14 @@ def get_id_required_nodes(referenced_nodes=False, nodes=None): def get_id(node): - """ - Get the `cbId` attribute of the given node + """Get the `cbId` attribute of the given node. + Args: node (str): the name of the node to retrieve the attribute from - Returns: str """ - if node is None: return @@ -2688,3 +2686,69 @@ def show_message(title, msg): pass else: message_window.message(title=title, message=msg, parent=parent) + + +def iter_shader_edits(relationships, shader_nodes, nodes_by_id, label=None): + """Yield edits as a set of actions.""" + + attributes = relationships.get("attributes", []) + shader_data = relationships.get("relationships", {}) + + shading_engines = cmds.ls(shader_nodes, type="objectSet", long=True) + assert shading_engines, "Error in retrieving objectSets from reference" + + # region compute lookup + shading_engines_by_id = defaultdict(list) + for shad in shading_engines: + shading_engines_by_id[get_id(shad)].append(shad) + # endregion + + # region assign shading engines and other sets + for data in shader_data.values(): + # collect all unique IDs of the set members + shader_uuid = data["uuid"] + member_uuids = [ + (member["uuid"], member.get("components")) + for member in data["members"]] + + filtered_nodes = list() + for _uuid, components in member_uuids: + nodes = nodes_by_id.get(_uuid, None) + if nodes is None: + continue + + if components: + # Assign to the components + nodes = [".".join([node, components]) for node in nodes] + + filtered_nodes.extend(nodes) + + id_shading_engines = shading_engines_by_id[shader_uuid] + if not id_shading_engines: + log.error("{} - No shader found with cbId " + "'{}'".format(label, shader_uuid)) + continue + elif len(id_shading_engines) > 1: + log.error("{} - Skipping shader assignment. " + "More than one shader found with cbId " + "'{}'. (found: {})".format(label, shader_uuid, + id_shading_engines)) + continue + + if not filtered_nodes: + log.warning("{} - No nodes found for shading engine " + "'{}'".format(label, id_shading_engines[0])) + continue + + yield {"action": "assign", + "uuid": data["uuid"], + "nodes": filtered_nodes, + "shader": id_shading_engines[0]} + + for data in attributes: + nodes = nodes_by_id.get(data["uuid"], []) + attr_value = data["attributes"] + yield {"action": "setattr", + "uuid": data["uuid"], + "nodes": nodes, + "attributes": attr_value} diff --git a/openpype/hosts/maya/plugins/load/load_look.py b/openpype/hosts/maya/plugins/load/load_look.py index c39bbc497e..fca612eff4 100644 --- a/openpype/hosts/maya/plugins/load/load_look.py +++ b/openpype/hosts/maya/plugins/load/load_look.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- +"""Look loader.""" import openpype.hosts.maya.api.plugin from avalon import api, io import json diff --git a/openpype/hosts/maya/plugins/load/load_vrayproxy.py b/openpype/hosts/maya/plugins/load/load_vrayproxy.py index 2bff6e0a77..d5d4a941e3 100644 --- a/openpype/hosts/maya/plugins/load/load_vrayproxy.py +++ b/openpype/hosts/maya/plugins/load/load_vrayproxy.py @@ -1,12 +1,21 @@ -from avalon.maya import lib -from avalon import api -from openpype.api import get_project_settings +# -*- coding: utf-8 -*- +"""Loader for Vray Proxy files. + +If there are Alembics published along vray proxy (in the same version), +loader will use them instead of native vray vrmesh format. + +""" import os + import maya.cmds as cmds +from avalon.maya import lib +from avalon import api, io +from openpype.api import get_project_settings + class VRayProxyLoader(api.Loader): - """Load VRayMesh proxy""" + """Load VRay Proxy with Alembic or VrayMesh.""" families = ["vrayproxy"] representations = ["vrmesh"] @@ -16,8 +25,17 @@ class VRayProxyLoader(api.Loader): icon = "code-fork" color = "orange" - def load(self, context, name, namespace, data): + def load(self, context, name=None, namespace=None, options=None): + # type: (dict, str, str, dict) -> None + """Loader entry point. + Args: + context (dict): Loaded representation context. + name (str): Name of container. + namespace (str): Optional namespace name. + options (dict): Optional loader options. + + """ from avalon.maya.pipeline import containerise from openpype.hosts.maya.api.lib import namespaced @@ -26,6 +44,9 @@ class VRayProxyLoader(api.Loader): except ValueError: family = "vrayproxy" + # get all representations for this version + self.fname = self._get_abc(context["version"]["_id"]) or self.fname + asset_name = context['asset']["name"] namespace = namespace or lib.unique_namespace( asset_name + "_", @@ -39,8 +60,8 @@ class VRayProxyLoader(api.Loader): with lib.maintained_selection(): cmds.namespace(addNamespace=namespace) with namespaced(namespace, new=False): - nodes, group_node = self.create_vray_proxy(name, - filename=self.fname) + nodes, group_node = self.create_vray_proxy( + name, filename=self.fname) self[:] = nodes if not nodes: @@ -63,7 +84,8 @@ class VRayProxyLoader(api.Loader): loader=self.__class__.__name__) def update(self, container, representation): - + # type: (dict, dict) -> None + """Update container with specified representation.""" node = container['objectName'] assert cmds.objExists(node), "Missing container" @@ -71,7 +93,8 @@ class VRayProxyLoader(api.Loader): vraymeshes = cmds.ls(members, type="VRayMesh") assert vraymeshes, "Cannot find VRayMesh in container" - filename = api.get_representation_path(representation) + # get all representations for this version + filename = self._get_abc(representation["parent"]) or api.get_representation_path(representation) # noqa: E501 for vray_mesh in vraymeshes: cmds.setAttr("{}.fileName".format(vray_mesh), @@ -84,7 +107,8 @@ class VRayProxyLoader(api.Loader): type="string") def remove(self, container): - + # type: (dict) -> None + """Remove loaded container.""" # Delete container and its contents if cmds.objExists(container['objectName']): members = cmds.sets(container['objectName'], query=True) or [] @@ -101,61 +125,62 @@ class VRayProxyLoader(api.Loader): "still has members: %s", namespace) def switch(self, container, representation): + # type: (dict, dict) -> None + """Switch loaded representation.""" self.update(container, representation) def create_vray_proxy(self, name, filename): + # type: (str, str) -> (list, str) """Re-create the structure created by VRay to support vrmeshes Args: - name(str): name of the asset + name (str): Name of the asset. + filename (str): File name of vrmesh. Returns: nodes(list) + """ - # Create nodes - vray_mesh = cmds.createNode('VRayMesh', name="{}_VRMS".format(name)) - mesh_shape = cmds.createNode("mesh", name="{}_GEOShape".format(name)) - vray_mat = cmds.shadingNode("VRayMeshMaterial", asShader=True, - name="{}_VRMM".format(name)) - vray_mat_sg = cmds.sets(name="{}_VRSG".format(name), - empty=True, - renderable=True, - noSurfaceShader=True) + if name is None: + name = os.path.splitext(os.path.basename(filename))[0] - cmds.setAttr("{}.fileName".format(vray_mesh), - filename, - type="string") + parent = cmds.createNode("transform", name=name) + proxy = cmds.createNode( + "VRayProxy", name="{}Shape".format(name), parent=parent) + cmds.setAttr(proxy + ".fileName", filename, type="string") + cmds.connectAttr("time1.outTime", proxy + ".currentFrame") - # Create important connections - cmds.connectAttr("time1.outTime", - "{0}.currentFrame".format(vray_mesh)) - cmds.connectAttr("{}.fileName2".format(vray_mesh), - "{}.fileName".format(vray_mat)) - cmds.connectAttr("{}.instancing".format(vray_mesh), - "{}.instancing".format(vray_mat)) - cmds.connectAttr("{}.output".format(vray_mesh), - "{}.inMesh".format(mesh_shape)) - cmds.connectAttr("{}.overrideFileName".format(vray_mesh), - "{}.overrideFileName".format(vray_mat)) - cmds.connectAttr("{}.currentFrame".format(vray_mesh), - "{}.currentFrame".format(vray_mat)) + return [parent, proxy], parent - # Set surface shader input - cmds.connectAttr("{}.outColor".format(vray_mat), - "{}.surfaceShader".format(vray_mat_sg)) + def _get_abc(self, version_id): + # type: (str) -> str + """Get abc representation file path if present. - # Connect mesh to shader - cmds.sets([mesh_shape], addElement=vray_mat_sg) + If here is published Alembic (abc) representation published along + vray proxy, get is file path. - group_node = cmds.group(empty=True, name="{}_GRP".format(name)) - mesh_transform = cmds.listRelatives(mesh_shape, - parent=True, fullPath=True) - cmds.parent(mesh_transform, group_node) - nodes = [vray_mesh, mesh_shape, vray_mat, vray_mat_sg, group_node] + Args: + version_id (str): Version hash id. - # Fix: Force refresh so the mesh shows correctly after creation - cmds.refresh() - cmds.setAttr("{}.geomType".format(vray_mesh), 2) + Returns: + str: Path to file. + None: If abc not found. - return nodes, group_node + """ + self.log.debug( + "Looking for abc in published representations of this version.") + abc_rep = io.find_one( + { + "type": "representation", + "parent": io.ObjectId(version_id), + "name": "abc" + }) + + if abc_rep: + self.log.debug("Found, we'll link alembic to vray proxy.") + file_name = api.get_representation_path(abc_rep) + self.log.debug("File: {}".format(self.fname)) + return file_name + + return "" diff --git a/openpype/hosts/maya/plugins/publish/collect_vrayproxy.py b/openpype/hosts/maya/plugins/publish/collect_vrayproxy.py new file mode 100644 index 0000000000..236797ca3c --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/collect_vrayproxy.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +"""Collect Vray Proxy.""" +import pyblish.api + + +class CollectVrayProxy(pyblish.api.InstancePlugin): + """Collect Vray Proxy instance. + + Add `pointcache` family for it. + """ + order = pyblish.api.CollectorOrder + 0.01 + label = 'Collect Vray Proxy' + families = ["vrayproxy"] + + def process(self, instance): + """Collector entry point.""" + if not instance.data.get('families'): + instance.data["families"] = [] diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index dea52f2154..ba716c0d18 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -18,7 +18,8 @@ class ExtractAlembic(openpype.api.Extractor): label = "Extract Pointcache (Alembic)" hosts = ["maya"] families = ["pointcache", - "model"] + "model", + "vrayproxy"] def process(self, instance): diff --git a/openpype/hosts/resolve/api/lib.py b/openpype/hosts/resolve/api/lib.py index 101e7bb572..bfe451aaa0 100644 --- a/openpype/hosts/resolve/api/lib.py +++ b/openpype/hosts/resolve/api/lib.py @@ -164,11 +164,21 @@ def create_media_pool_item(fpath: str, # try to search in bin if the clip does not exist existing_mpi = get_media_pool_item(fpath, root_bin) + print(">>>>> existing_mpi: {}".format(existing_mpi)) if not existing_mpi: - media_pool_item = media_storage.AddItemsToMediaPool(fpath) - print(media_pool_item) + print("___ fpath: {}".format(fpath)) + dirname, file = os.path.split(fpath) + _name, ext = os.path.splitext(file) + print(dirname) + media_pool_items = media_storage.AddItemListToMediaPool(os.path.normpath(dirname)) + print(media_pool_items) # pop the returned dict on first item as resolve data object is such - return media_pool_item.pop(1.0) + if media_pool_items: + media_pool_item = [mpi for mpi in media_pool_items + if ext in mpi.GetClipProperty("File Path")] + return media_pool_item.pop() + else: + return False else: return existing_mpi @@ -189,7 +199,8 @@ def get_media_pool_item(fpath, root: object = None) -> object: fname = os.path.basename(fpath) for _mpi in root.GetClipList(): - _mpi_name = _mpi.GetClipProperty("File Name")["File Name"] + print(">>> _mpi: {}".format(_mpi.GetClipProperty("File Name"))) + _mpi_name = _mpi.GetClipProperty("File Name") _mpi_name = get_reformated_path(_mpi_name, first=True) if fname in _mpi_name: return _mpi @@ -215,8 +226,8 @@ def create_timeline_item(media_pool_item: object, # get all variables project = get_current_project() media_pool = project.GetMediaPool() - clip_property = media_pool_item.GetClipProperty() - clip_name = clip_property["File Name"] + _clip_property = media_pool_item.GetClipProperty + clip_name = _clip_property("File Name") timeline = timeline or get_current_timeline() # if timeline was used then switch it to current timeline @@ -231,7 +242,6 @@ def create_timeline_item(media_pool_item: object, clip_data.update({"endFrame": source_end}) print(clip_data) - print(clip_property) # add to timeline media_pool.AppendToTimeline([clip_data]) @@ -257,8 +267,8 @@ def get_timeline_item(media_pool_item: object, Returns: object: resolve.TimelineItem """ - clip_property = media_pool_item.GetClipProperty() - clip_name = clip_property["File Name"] + _clip_property = media_pool_item.GetClipProperty + clip_name = _clip_property("File Name") output_timeline_item = None timeline = timeline or get_current_timeline() @@ -267,8 +277,8 @@ def get_timeline_item(media_pool_item: object, for _ti_data in get_current_timeline_items(): _ti_clip = _ti_data["clip"]["item"] - _ti_clip_property = _ti_clip.GetMediaPoolItem().GetClipProperty() - if clip_name in _ti_clip_property["File Name"]: + _ti_clip_property = _ti_clip.GetMediaPoolItem().GetClipProperty + if clip_name in _ti_clip_property("File Name"): output_timeline_item = _ti_clip return output_timeline_item @@ -541,15 +551,15 @@ def create_compound_clip(clip_data, name, folder): clip_attributes = get_clip_attributes(clip_item) mp_item = clip_item.GetMediaPoolItem() - mp_props = mp_item.GetClipProperty() + _mp_props = mp_item.GetClipProperty - mp_first_frame = int(mp_props["Start"]) - mp_last_frame = int(mp_props["End"]) + mp_first_frame = int(_mp_props("Start")) + mp_last_frame = int(_mp_props("End")) # initialize basic source timing for otio ci_l_offset = clip_item.GetLeftOffset() ci_duration = clip_item.GetDuration() - rate = float(mp_props["FPS"]) + rate = float(_mp_props("FPS")) # source rational times mp_in_rc = opentime.RationalTime((ci_l_offset), rate) @@ -606,7 +616,7 @@ def create_compound_clip(clip_data, name, folder): cct.SetMetadata(self.pype_tag_name, clip_attributes) # reset start timecode of the compound clip - cct.SetClipProperty("Start TC", mp_props["Start TC"]) + cct.SetClipProperty("Start TC", _mp_props("Start TC")) # swap clips on timeline swap_clips(clip_item, cct, in_frame, out_frame) @@ -632,8 +642,8 @@ def swap_clips(from_clip, to_clip, to_in_frame, to_out_frame): bool: True if successfully replaced """ - clip_prop = to_clip.GetClipProperty() - to_clip_name = clip_prop["File Name"] + _clip_prop = to_clip.GetClipProperty + to_clip_name = _clip_prop("File Name") # add clip item as take to timeline take = from_clip.AddTake( to_clip, diff --git a/openpype/hosts/resolve/api/plugin.py b/openpype/hosts/resolve/api/plugin.py index 4712d0a8b9..488e9fca07 100644 --- a/openpype/hosts/resolve/api/plugin.py +++ b/openpype/hosts/resolve/api/plugin.py @@ -379,9 +379,10 @@ class ClipLoader: # create mediaItem in active project bin # create clip media + media_pool_item = lib.create_media_pool_item( self.data["path"], self.active_bin) - clip_property = media_pool_item.GetClipProperty() + _clip_property = media_pool_item.GetClipProperty # get handles handle_start = self.data["versionData"].get("handleStart") @@ -391,10 +392,10 @@ class ClipLoader: if handle_end is None: handle_end = int(self.data["assetData"]["handleEnd"]) - source_in = int(clip_property["Start"]) - source_out = int(clip_property["End"]) + source_in = int(_clip_property("Start")) + source_out = int(_clip_property("End")) - if clip_property["Type"] == "Video": + if _clip_property("Type") == "Video": source_in += handle_start source_out -= handle_end @@ -420,8 +421,8 @@ class ClipLoader: # create clip media media_pool_item = lib.create_media_pool_item( self.data["path"], self.active_bin) - clip_property = media_pool_item.GetClipProperty() - clip_name = clip_property["File Name"] + _clip_property = media_pool_item.GetClipProperty + clip_name = _clip_property("File Name") # get handles handle_start = self.data["versionData"].get("handleStart") @@ -431,8 +432,8 @@ class ClipLoader: if handle_end is None: handle_end = int(self.data["assetData"]["handleEnd"]) - source_in = int(clip_property["Start"]) - source_out = int(clip_property["End"]) + source_in = int(_clip_property("Start")) + source_out = int(_clip_property("End")) resolve.swap_clips( timeline_item, diff --git a/openpype/hosts/resolve/otio/davinci_export.py b/openpype/hosts/resolve/otio/davinci_export.py index 7912b1abd8..2c276d9888 100644 --- a/openpype/hosts/resolve/otio/davinci_export.py +++ b/openpype/hosts/resolve/otio/davinci_export.py @@ -33,8 +33,11 @@ def create_otio_time_range(start_frame, frame_duration, fps): def create_otio_reference(media_pool_item): metadata = _get_metadata_media_pool_item(media_pool_item) - mp_clip_property = media_pool_item.GetClipProperty() - path = mp_clip_property["File Path"] + print("media pool item: {}".format(media_pool_item.GetName())) + + _mp_clip_property = media_pool_item.GetClipProperty + + path = _mp_clip_property("File Path") reformat_path = utils.get_reformated_path(path, padded=True) padding = utils.get_padding_from_path(path) @@ -45,13 +48,12 @@ def create_otio_reference(media_pool_item): }) # get clip property regarding to type - mp_clip_property = media_pool_item.GetClipProperty() - fps = float(mp_clip_property["FPS"]) - if mp_clip_property["Type"] == "Video": - frame_start = int(mp_clip_property["Start"]) - frame_duration = int(mp_clip_property["Frames"]) + fps = float(_mp_clip_property("FPS")) + if _mp_clip_property("Type") == "Video": + frame_start = int(_mp_clip_property("Start")) + frame_duration = int(_mp_clip_property("Frames")) else: - audio_duration = str(mp_clip_property["Duration"]) + audio_duration = str(_mp_clip_property("Duration")) frame_start = 0 frame_duration = int(utils.timecode_to_frames( audio_duration, float(fps))) @@ -124,10 +126,10 @@ def create_otio_markers(track_item, fps): def create_otio_clip(track_item): media_pool_item = track_item.GetMediaPoolItem() - mp_clip_property = media_pool_item.GetClipProperty() + _mp_clip_property = media_pool_item.GetClipProperty if not self.project_fps: - fps = mp_clip_property["FPS"] + fps = float(_mp_clip_property("FPS")) else: fps = self.project_fps @@ -140,9 +142,9 @@ def create_otio_clip(track_item): fps ) - if mp_clip_property["Type"] == "Audio": + if _mp_clip_property("Type") == "Audio": return_clips = list() - audio_chanels = mp_clip_property["Audio Ch"] + audio_chanels = _mp_clip_property("Audio Ch") for channel in range(0, int(audio_chanels)): clip = otio.schema.Clip( name=f"{name}_{channel}", diff --git a/openpype/hosts/resolve/plugins/load/load_clip.py b/openpype/hosts/resolve/plugins/load/load_clip.py index e2e1c50365..e20384ee6c 100644 --- a/openpype/hosts/resolve/plugins/load/load_clip.py +++ b/openpype/hosts/resolve/plugins/load/load_clip.py @@ -1,7 +1,10 @@ from avalon import io, api from openpype.hosts import resolve from copy import deepcopy - +from importlib import reload +from openpype.hosts.resolve.api import lib, plugin +reload(plugin) +reload(lib) class LoadClip(resolve.TimelineItemLoader): """Load a subset to timeline as clip diff --git a/openpype/hosts/resolve/utility_scripts/tests/testing_load_media_pool_item.py b/openpype/hosts/resolve/utility_scripts/tests/testing_load_media_pool_item.py new file mode 100644 index 0000000000..cfdbe890e5 --- /dev/null +++ b/openpype/hosts/resolve/utility_scripts/tests/testing_load_media_pool_item.py @@ -0,0 +1,22 @@ +#! python3 +import avalon.api as avalon +import openpype +import openpype.hosts.resolve as bmdvr + + +def file_processing(fpath): + media_pool_item = bmdvr.create_media_pool_item(fpath) + print(media_pool_item) + + track_item = bmdvr.create_timeline_item(media_pool_item) + print(track_item) + + +if __name__ == "__main__": + path = "C:/CODE/__openpype_projects/jtest03dev/shots/sq01/mainsq01sh030/publish/plate/plateMain/v006/jt3d_mainsq01sh030_plateMain_v006.0996.exr" + + openpype.install() + # activate resolve from openpype + avalon.install(bmdvr) + + file_processing(path) \ No newline at end of file diff --git a/openpype/modules/sync_server/sync_server_module.py b/openpype/modules/sync_server/sync_server_module.py index 5645cdfbec..b50bf19dca 100644 --- a/openpype/modules/sync_server/sync_server_module.py +++ b/openpype/modules/sync_server/sync_server_module.py @@ -413,11 +413,13 @@ class SyncServerModule(PypeModule, ITrayModule): def get_enabled_projects(self): """Returns list of projects which have SyncServer enabled.""" enabled_projects = [] - for project in self.connection.projects(): - project_name = project["name"] - project_settings = self.get_sync_project_setting(project_name) - if project_settings: - enabled_projects.append(project_name) + + if self.enabled: + for project in self.connection.projects(): + project_name = project["name"] + project_settings = self.get_sync_project_setting(project_name) + if project_settings: + enabled_projects.append(project_name) return enabled_projects """ End of Public API """ diff --git a/openpype/plugins/publish/extract_burnin.py b/openpype/plugins/publish/extract_burnin.py index e266c39714..c37fa7390a 100644 --- a/openpype/plugins/publish/extract_burnin.py +++ b/openpype/plugins/publish/extract_burnin.py @@ -1,19 +1,24 @@ import os import re +import subprocess import json import copy import tempfile +import platform +import shutil + +import clique +import six +import pyblish import openpype import openpype.api -import pyblish from openpype.lib import ( get_pype_execute_args, should_decompress, get_decompress_dir, decompress ) -import shutil class ExtractBurnin(openpype.api.Extractor): @@ -48,18 +53,17 @@ class ExtractBurnin(openpype.api.Extractor): ] # Default options for burnins for cases that are not set in presets. default_options = { - "opacity": 1, - "x_offset": 5, - "y_offset": 5, + "font_size": 42, + "font_color": [255, 255, 255, 255], + "bg_color": [0, 0, 0, 127], "bg_padding": 5, - "bg_opacity": 0.5, - "font_size": 42 + "x_offset": 5, + "y_offset": 5 } # Preset attributes profiles = None options = None - fields = None def process(self, instance): # ffmpeg doesn't support multipart exrs @@ -103,7 +107,7 @@ class ExtractBurnin(openpype.api.Extractor): return # Pre-filter burnin definitions by instance families - burnin_defs = self.filter_burnins_by_families(profile, instance) + burnin_defs = self.filter_burnins_defs(profile, instance) if not burnin_defs: self.log.info(( "Skipped instance. Burnin definitions are not set for profile" @@ -111,19 +115,7 @@ class ExtractBurnin(openpype.api.Extractor): ).format(host_name, family, task_name, profile)) return - # Prepare burnin options - profile_options = copy.deepcopy(self.default_options) - for key, value in (self.options or {}).items(): - if value is not None: - profile_options[key] = value - - # Prepare global burnin values from presets - profile_burnins = {} - for key, value in (self.fields or {}).items(): - key_low = key.lower() - if key_low in self.positions: - if value is not None: - profile_burnins[key_low] = value + burnin_options = self._get_burnin_options() # Prepare basic data for processing _burnin_data, _temp_data = self.prepare_basic_data(instance) @@ -134,11 +126,6 @@ class ExtractBurnin(openpype.api.Extractor): # [pype executable, *pype script, "run"] executable_args = get_pype_execute_args("run", scriptpath) - # Environments for script process - env = os.environ.copy() - # pop PYTHONPATH - env.pop("PYTHONPATH", None) - for idx, repre in enumerate(tuple(instance.data["representations"])): self.log.debug("repre ({}): `{}`".format(idx + 1, repre["name"])) if not self.repres_is_valid(repre): @@ -184,26 +171,11 @@ class ExtractBurnin(openpype.api.Extractor): elif "ftrackreview" in new_repre["tags"]: new_repre["tags"].remove("ftrackreview") - burnin_options = copy.deepcopy(profile_options) - burnin_values = copy.deepcopy(profile_burnins) - - # Options overrides - for key, value in (burnin_def.get("options") or {}).items(): - # Set or override value if is valid - if value is not None: - burnin_options[key] = value - - # Burnin values overrides - for key, value in burnin_def.items(): - key_low = key.lower() - if key_low in self.positions: - if value is not None: - # Set or override value if is valid - burnin_values[key_low] = value - - elif key_low in burnin_values: - # Pop key if value is set to None (null in json) - burnin_values.pop(key_low) + burnin_values = {} + for key in self.positions: + value = burnin_def.get(key) + if value: + burnin_values[key] = value # Remove "delete" tag from new representation if "delete" in new_repre["tags"]: @@ -214,7 +186,8 @@ class ExtractBurnin(openpype.api.Extractor): # able have multiple outputs in case of more burnin presets # Join previous "outputName" with filename suffix new_name = "_".join( - [new_repre["outputName"], filename_suffix]) + [new_repre["outputName"], filename_suffix] + ) new_repre["name"] = new_name new_repre["outputName"] = new_name @@ -246,8 +219,10 @@ class ExtractBurnin(openpype.api.Extractor): "input": temp_data["full_input_path"], "output": temp_data["full_output_path"], "burnin_data": burnin_data, - "options": burnin_options, - "values": burnin_values + "options": copy.deepcopy(burnin_options), + "values": burnin_values, + "full_input_path": temp_data["full_input_paths"][0], + "first_frame": temp_data["first_frame"] } self.log.debug( @@ -273,10 +248,16 @@ class ExtractBurnin(openpype.api.Extractor): self.log.debug("Executing: {}".format(" ".join(args))) # Run burnin script - openpype.api.run_subprocess( - args, shell=True, logger=self.log, env=env - ) + process_kwargs = { + "logger": self.log, + "env": {} + } + if platform.system().lower() == "windows": + process_kwargs["creationflags"] = ( + subprocess.CREATE_NO_WINDOW + ) + openpype.api.run_subprocess(args, **process_kwargs) # Remove the temporary json os.remove(temporary_json_filepath) @@ -301,6 +282,57 @@ class ExtractBurnin(openpype.api.Extractor): if do_decompress and os.path.exists(decompressed_dir): shutil.rmtree(decompressed_dir) + def _get_burnin_options(self): + # Prepare burnin options + burnin_options = copy.deepcopy(self.default_options) + if self.options: + for key, value in self.options.items(): + if value is not None: + burnin_options[key] = copy.deepcopy(value) + + # Convert colors defined as list of numbers RGBA (0-255) + # BG Color + bg_color = burnin_options.get("bg_color") + if bg_color and isinstance(bg_color, list): + bg_red, bg_green, bg_blue, bg_alpha = bg_color + bg_color_hex = "#{0:0>2X}{1:0>2X}{2:0>2X}".format( + bg_red, bg_green, bg_blue + ) + bg_color_alpha = float(bg_alpha) / 255 + burnin_options["bg_opacity"] = bg_color_alpha + burnin_options["bg_color"] = bg_color_hex + + # FG Color + font_color = burnin_options.get("font_color") + if font_color and isinstance(font_color, list): + fg_red, fg_green, fg_blue, fg_alpha = font_color + fg_color_hex = "#{0:0>2X}{1:0>2X}{2:0>2X}".format( + fg_red, fg_green, fg_blue + ) + fg_color_alpha = float(fg_alpha) / 255 + burnin_options["opacity"] = fg_color_alpha + burnin_options["font_color"] = fg_color_hex + + # Define font filepath + # - font filepath may be defined in settings + font_filepath = burnin_options.get("font_filepath") + if font_filepath and isinstance(font_filepath, dict): + sys_name = platform.system().lower() + font_filepath = font_filepath.get(sys_name) + + if font_filepath and isinstance(font_filepath, six.string_types): + font_filepath = font_filepath.format(**os.environ) + if not os.path.exists(font_filepath): + font_filepath = None + + # Use OpenPype default font + if not font_filepath: + font_filepath = openpype.api.resources.get_liberation_font_path() + + burnin_options["font"] = font_filepath + + return burnin_options + def prepare_basic_data(self, instance): """Pick data from instance for processing and for burnin strings. @@ -419,23 +451,15 @@ class ExtractBurnin(openpype.api.Extractor): list: Containg all burnin definitions matching entered tags. """ filtered_burnins = {} - repre_tags_low = [tag.lower() for tag in tags] + repre_tags_low = set(tag.lower() for tag in tags) for filename_suffix, burnin_def in burnin_defs.items(): valid = True - output_filters = burnin_def.get("filter") - if output_filters: + tag_filters = burnin_def["filter"]["tags"] + if tag_filters: # Check tag filters - tag_filters = output_filters.get("tags") - if tag_filters: - tag_filters_low = [tag.lower() for tag in tag_filters] - valid = False - for tag in repre_tags_low: - if tag in tag_filters_low: - valid = True - break + tag_filters_low = set(tag.lower() for tag in tag_filters) - if not valid: - continue + valid = bool(repre_tags_low & tag_filters_low) if valid: filtered_burnins[filename_suffix] = burnin_def @@ -461,32 +485,47 @@ class ExtractBurnin(openpype.api.Extractor): None: This is processing method. """ # TODO we should find better way to know if input is sequence - is_sequence = ( - "sequence" in new_repre["tags"] - and isinstance(new_repre["files"], (tuple, list)) - ) + input_filenames = new_repre["files"] + is_sequence = False + if isinstance(input_filenames, (tuple, list)): + if len(input_filenames) > 1: + is_sequence = True + + # Sequence must have defined first frame + # - not used if input is not a sequence + first_frame = None if is_sequence: - input_filename = new_repre["sequence_file"] - else: - input_filename = new_repre["files"] + collections, _ = clique.assemble(input_filenames) + if not collections: + is_sequence = False + else: + input_filename = new_repre["sequence_file"] + collection = collections[0] + indexes = list(collection.indexes) + padding = len(str(max(indexes))) + head = collection.format("{head}") + tail = collection.format("{tail}") + output_filename = "{}%{:0>2}d{}{}".format( + head, padding, filename_suffix, tail + ) + repre_files = [] + for idx in indexes: + repre_files.append(output_filename % idx) - filepart_start, ext = os.path.splitext(input_filename) - dir_path, basename = os.path.split(filepart_start) + first_frame = min(indexes) - if is_sequence: - # NOTE modified to keep name when multiple dots are in name - basename_parts = basename.split(".") - frame_part = basename_parts.pop(-1) + if not is_sequence: + input_filename = input_filenames + if isinstance(input_filename, (tuple, list)): + input_filename = input_filename[0] - basename_start = ".".join(basename_parts) + filename_suffix - new_basename = ".".join((basename_start, frame_part)) - output_filename = new_basename + ext - - else: + filepart_start, ext = os.path.splitext(input_filename) + dir_path, basename = os.path.split(filepart_start) output_filename = basename + filename_suffix + ext + if dir_path: + output_filename = os.path.join(dir_path, output_filename) - if dir_path: - output_filename = os.path.join(dir_path, output_filename) + repre_files = output_filename stagingdir = new_repre["stagingDir"] full_input_path = os.path.join( @@ -498,6 +537,9 @@ class ExtractBurnin(openpype.api.Extractor): temp_data["full_input_path"] = full_input_path temp_data["full_output_path"] = full_output_path + temp_data["first_frame"] = first_frame + + new_repre["files"] = repre_files self.log.debug("full_input_path: {}".format(full_input_path)) self.log.debug("full_output_path: {}".format(full_output_path)) @@ -505,17 +547,16 @@ class ExtractBurnin(openpype.api.Extractor): # Prepare full paths to input files and filenames for reprensetation full_input_paths = [] if is_sequence: - repre_files = [] - for frame_index in range(1, temp_data["duration"] + 1): - repre_files.append(output_filename % frame_index) - full_input_paths.append(full_input_path % frame_index) + for filename in input_filenames: + filepath = os.path.join( + os.path.normpath(stagingdir), filename + ).replace("\\", "/") + full_input_paths.append(filepath) else: full_input_paths.append(full_input_path) - repre_files = output_filename temp_data["full_input_paths"] = full_input_paths - new_repre["files"] = repre_files def prepare_repre_data(self, instance, repre, burnin_data, temp_data): """Prepare data for representation. @@ -694,17 +735,16 @@ class ExtractBurnin(openpype.api.Extractor): final_profile.pop("__value__") return final_profile - def filter_burnins_by_families(self, profile, instance): - """Filter outputs that are not supported for instance families. + def filter_burnins_defs(self, profile, instance): + """Filter outputs by their values from settings. - Output definitions without families filter are marked as valid. + Output definitions with at least one value are marked as valid. Args: profile (dict): Profile from presets matching current context. - families (list): All families of current instance. Returns: - list: Containg all output definitions matching entered families. + list: Containg all valid output definitions. """ filtered_burnin_defs = {} @@ -712,21 +752,52 @@ class ExtractBurnin(openpype.api.Extractor): if not burnin_defs: return filtered_burnin_defs - # Prepare families families = self.families_from_instance(instance) - families = [family.lower() for family in families] + low_families = [family.lower() for family in families] - for filename_suffix, burnin_def in burnin_defs.items(): - burnin_filter = burnin_def.get("filter") - # When filters not set then skip filtering process - if burnin_filter: - families_filters = burnin_filter.get("families") - if not self.families_filter_validation( - families, families_filters - ): - continue + for filename_suffix, orig_burnin_def in burnin_defs.items(): + burnin_def = copy.deepcopy(orig_burnin_def) + def_filter = burnin_def.get("filter", None) or {} + for key in ("families", "tags"): + if key not in def_filter: + def_filter[key] = [] + + families_filters = def_filter["families"] + if not self.families_filter_validation( + low_families, families_filters + ): + self.log.debug(( + "Skipped burnin definition \"{}\". Family" + " fiters ({}) does not match current instance families: {}" + ).format( + filename_suffix, str(families_filters), str(families) + )) + continue + + # Burnin values + burnin_values = {} + for key, value in tuple(burnin_def.items()): + key_low = key.lower() + if key_low in self.positions and value: + burnin_values[key_low] = value + + # Skip processing if burnin values are not set + if not burnin_values: + self.log.warning(( + "Burnin values for Burnin definition \"{}\"" + " are not filled. Definition will be skipped." + " Origin value: {}" + ).format(filename_suffix, str(orig_burnin_def))) + continue + + burnin_values["filter"] = def_filter + + filtered_burnin_defs[filename_suffix] = burnin_values + + self.log.debug(( + "Burnin definition \"{}\" passed first filtering." + ).format(filename_suffix)) - filtered_burnin_defs[filename_suffix] = burnin_def return filtered_burnin_defs def families_filter_validation(self, families, output_families_filter): diff --git a/openpype/scripts/otio_burnin.py b/openpype/scripts/otio_burnin.py index 5e2a22f1b5..8826f1af0c 100644 --- a/openpype/scripts/otio_burnin.py +++ b/openpype/scripts/otio_burnin.py @@ -5,7 +5,6 @@ import subprocess import platform import json import opentimelineio_contrib.adapters.ffmpeg_burnins as ffmpeg_burnins -from openpype.api import resources import openpype.lib @@ -14,7 +13,7 @@ ffprobe_path = openpype.lib.get_ffmpeg_tool_path("ffprobe") FFMPEG = ( - '"{}" -i "%(input)s" %(filters)s %(args)s%(output)s' + '"{}"%(input_args)s -i "%(input)s" %(filters)s %(args)s%(output)s' ).format(ffmpeg_path) FFPROBE = ( @@ -121,10 +120,15 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): 'font_size': 42 } - def __init__(self, source, streams=None, options_init=None): + def __init__( + self, source, streams=None, options_init=None, first_frame=None + ): if not streams: streams = _streams(source) + self.first_frame = first_frame + self.input_args = [] + super().__init__(source, streams) if options_init: @@ -236,31 +240,26 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): timecode_text = options.get("timecode") or "" text_for_size += timecode_text + font_path = options.get("font") + if not font_path or not os.path.exists(font_path): + font_path = ffmpeg_burnins.FONT + + options["font"] = font_path + data.update(options) - - os_system = platform.system().lower() - data_font = data.get("font") - if not data_font: - data_font = ( - resources.get_liberation_font_path().replace("\\", "/") - ) - elif isinstance(data_font, dict): - data_font = data_font[os_system] - - if data_font: - data["font"] = data_font - options["font"] = data_font - if ffmpeg_burnins._is_windows(): - data["font"] = ( - data_font - .replace(os.sep, r'\\' + os.sep) - .replace(':', r'\:') - ) - data.update( ffmpeg_burnins._drawtext(align, resolution, text_for_size, options) ) + arg_font_path = font_path + if platform.system().lower() == "windows": + arg_font_path = ( + arg_font_path + .replace(os.sep, r'\\' + os.sep) + .replace(':', r'\:') + ) + data["font"] = arg_font_path + self.filters['drawtext'].append(draw % data) if options.get('bg_color') is not None: @@ -289,7 +288,21 @@ class ModifiedBurnins(ffmpeg_burnins.Burnins): if self.filter_string: filters = '-vf "{}"'.format(self.filter_string) + if self.first_frame is not None: + start_number_arg = "-start_number {}".format(self.first_frame) + self.input_args.append(start_number_arg) + if "start_number" not in args: + if not args: + args = start_number_arg + else: + args = " ".join((start_number_arg, args)) + + input_args = "" + if self.input_args: + input_args = " {}".format(" ".join(self.input_args)) + return (FFMPEG % { + 'input_args': input_args, 'input': self.source, 'output': output, 'args': '%s ' % args if args else '', @@ -370,7 +383,8 @@ def example(input_path, output_path): def burnins_from_data( input_path, output_path, data, - codec_data=None, options=None, burnin_values=None, overwrite=True + codec_data=None, options=None, burnin_values=None, overwrite=True, + full_input_path=None, first_frame=None ): """This method adds burnins to video/image file based on presets setting. @@ -427,8 +441,11 @@ def burnins_from_data( "shot": "sh0010" } """ + streams = None + if full_input_path: + streams = _streams(full_input_path) - burnin = ModifiedBurnins(input_path, options_init=options) + burnin = ModifiedBurnins(input_path, streams, options, first_frame) frame_start = data.get("frame_start") frame_end = data.get("frame_end") @@ -591,6 +608,8 @@ if __name__ == "__main__": in_data["burnin_data"], codec_data=in_data.get("codec"), options=in_data.get("options"), - burnin_values=in_data.get("values") + burnin_values=in_data.get("values"), + full_input_path=in_data.get("full_input_path"), + first_frame=in_data.get("first_frame") ) print("* Burnin script has finished") diff --git a/openpype/settings/defaults/project_anatomy/attributes.json b/openpype/settings/defaults/project_anatomy/attributes.json index 921d12e32a..387e12bcea 100644 --- a/openpype/settings/defaults/project_anatomy/attributes.json +++ b/openpype/settings/defaults/project_anatomy/attributes.json @@ -14,7 +14,7 @@ "nuke/12-2", "nukex/12-2", "hiero/12-2", - "resolve/16", + "resolve/stable", "houdini/18-5", "blender/2-91", "harmony/20", diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 61db35ba79..d2213044f2 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -73,11 +73,26 @@ "enabled": true, "options": { "font_size": 42, - "opacity": 1.0, - "bg_opacity": 0.5, + "font_color": [ + 255, + 255, + 255, + 255 + ], + "bg_color": [ + 0, + 0, + 0, + 127 + ], "x_offset": 5, "y_offset": 5, - "bg_padding": 5 + "bg_padding": 5, + "font_filepath": { + "windows": "", + "darwin": "", + "linux": "" + } }, "profiles": [ { @@ -90,7 +105,11 @@ "TOP_RIGHT": "{anatomy[version]}", "BOTTOM_LEFT": "{username}", "BOTTOM_CENTERED": "{asset}", - "BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}" + "BOTTOM_RIGHT": "{frame_start}-{current_frame}-{frame_end}", + "filter": { + "families": [], + "tags": [] + } } } } diff --git a/openpype/settings/defaults/system_settings/applications.json b/openpype/settings/defaults/system_settings/applications.json index e77b5d0ce5..63d6da4633 100644 --- a/openpype/settings/defaults/system_settings/applications.json +++ b/openpype/settings/defaults/system_settings/applications.json @@ -758,9 +758,9 @@ "RESOLVE_DEV": "True" }, "variants": { - "16": { + "stable": { "enabled": true, - "variant_label": "16", + "variant_label": "stable", "use_python_2": false, "executables": { "windows": [ diff --git a/openpype/settings/entities/base_entity.py b/openpype/settings/entities/base_entity.py index 2b9fd02904..3e73fa8aa6 100644 --- a/openpype/settings/entities/base_entity.py +++ b/openpype/settings/entities/base_entity.py @@ -468,7 +468,7 @@ class BaseItemEntity(BaseEntity): return False # Skip if entity is under group - if self.group_item: + if self.group_item is not None: return False # Skip if is group and any children is already marked with studio @@ -495,7 +495,7 @@ class BaseItemEntity(BaseEntity): return False # Do not show on items under group item - if self.group_item: + if self.group_item is not None: return False # Skip if already is marked to save project overrides @@ -796,7 +796,8 @@ class ItemEntity(BaseItemEntity): # Group item reference if self.parent.is_group: self.group_item = self.parent - elif self.parent.group_item: + + elif self.parent.group_item is not None: self.group_item = self.parent.group_item self.key = self.schema_data.get("key") diff --git a/openpype/settings/entities/dict_mutable_keys_entity.py b/openpype/settings/entities/dict_mutable_keys_entity.py index 1f037ba5b4..ef0124c0f0 100644 --- a/openpype/settings/entities/dict_mutable_keys_entity.py +++ b/openpype/settings/entities/dict_mutable_keys_entity.py @@ -222,7 +222,7 @@ class DictMutableKeysEntity(EndpointEntity): if self.value_is_env_group: self.item_schema["env_group_key"] = "" - if not self.group_item: + if self.group_item is None: self.is_group = True def schema_validations(self): diff --git a/openpype/settings/entities/input_entities.py b/openpype/settings/entities/input_entities.py index 1b2e10ca1d..409e6a66b4 100644 --- a/openpype/settings/entities/input_entities.py +++ b/openpype/settings/entities/input_entities.py @@ -32,7 +32,7 @@ class EndpointEntity(ItemEntity): super(EndpointEntity, self).__init__(*args, **kwargs) if ( - not (self.group_item or self.is_group) + not (self.group_item is not None or self.is_group) and not (self.is_dynamic_item or self.is_in_dynamic_item) ): self.is_group = True @@ -410,6 +410,9 @@ class PathInput(InputEntity): self.valid_value_types = (STRING_TYPE, ) self.value_on_not_set = "" + # GUI attributes + self.placeholder_text = self.schema_data.get("placeholder") + class RawJsonEntity(InputEntity): schema_types = ["raw-json"] diff --git a/openpype/settings/entities/item_entities.py b/openpype/settings/entities/item_entities.py index c3c6c598f6..48336080b6 100644 --- a/openpype/settings/entities/item_entities.py +++ b/openpype/settings/entities/item_entities.py @@ -49,18 +49,21 @@ class PathEntity(ItemEntity): return self.child_obj.items() def _item_initalization(self): - if not self.group_item and not self.is_group: + if self.group_item is None and not self.is_group: self.is_group = True self.multiplatform = self.schema_data.get("multiplatform", False) self.multipath = self.schema_data.get("multipath", False) + placeholder_text = self.schema_data.get("placeholder") + # Create child object if not self.multiplatform and not self.multipath: valid_value_types = (STRING_TYPE, ) item_schema = { "type": "path-input", - "key": self.key + "key": self.key, + "placeholder": placeholder_text } elif not self.multiplatform: @@ -68,7 +71,10 @@ class PathEntity(ItemEntity): item_schema = { "type": "list", "key": self.key, - "object_type": "path-input" + "object_type": { + "type": "path-input", + "placeholder": placeholder_text + } } else: @@ -87,9 +93,13 @@ class PathEntity(ItemEntity): } if self.multipath: child_item["type"] = "list" - child_item["object_type"] = "path-input" + child_item["object_type"] = { + "type": "path-input", + "placeholder": placeholder_text + } else: child_item["type"] = "path-input" + child_item["placeholder"] = placeholder_text item_schema["children"].append(child_item) @@ -199,7 +209,7 @@ class ListStrictEntity(ItemEntity): # GUI attribute self.is_horizontal = self.schema_data.get("horizontal", True) - if not self.group_item and not self.is_group: + if self.group_item is None and not self.is_group: self.is_group = True def schema_validations(self): diff --git a/openpype/settings/entities/list_entity.py b/openpype/settings/entities/list_entity.py index aff3bf0156..a57468fff7 100644 --- a/openpype/settings/entities/list_entity.py +++ b/openpype/settings/entities/list_entity.py @@ -148,7 +148,7 @@ class ListEntity(EndpointEntity): item_schema = {"type": item_schema} self.item_schema = item_schema - if not self.group_item: + if self.group_item is None: self.is_group = True # Value that was set on set_override_state diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json index 1bd028ac79..2636594fad 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_publish.json @@ -101,30 +101,8 @@ "type": "text" }, { - "key": "tags", - "label": "Tags", - "type": "enum", - "multiselection": true, - "enum_items": [ - { - "burnin": "Add burnins" - }, - { - "ftrackreview": "Add to Ftrack" - }, - { - "delete": "Delete output" - }, - { - "slate-frame": "Add slate frame" - }, - { - "no-handles": "Skip handle frames" - }, - { - "sequence": "Output as image sequence" - } - ] + "type": "schema", + "name": "schema_representation_tags" }, { "key": "ffmpeg_args", @@ -301,20 +279,24 @@ "minimum": 0 }, { - "type": "number", - "key": "opacity", - "label": "Font opacity", - "decimal": 2, - "maximum": 1, - "minimum": 0 + "type": "schema_template", + "name": "template_rgba_color", + "template_data": [ + { + "label": "Font Color", + "name": "font_color" + } + ] }, { - "type": "number", - "key": "bg_opacity", - "label": "Background opacity", - "decimal": 2, - "maximum": 1, - "minimum": 0 + "type": "schema_template", + "name": "template_rgba_color", + "template_data": [ + { + "label": "Background Color", + "name": "bg_color" + } + ] }, { "type": "number", @@ -330,6 +312,13 @@ "type": "number", "key": "bg_padding", "label": "Padding aroung text" + }, + { + "type": "path", + "key": "font_filepath", + "label": "Font file path", + "multipath": false, + "multiplatform": true } ] }, @@ -396,6 +385,24 @@ "key": "BOTTOM_RIGHT", "label": "BottomRight", "type": "text" + }, + { + "key": "filter", + "label": "Additional filtering", + "type": "dict", + "highlight_content": true, + "children": [ + { + "key": "families", + "label": "Families", + "type": "list", + "object_type": "text" + }, + { + "type": "schema", + "name": "schema_representation_tags" + } + ] } ] } diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json new file mode 100644 index 0000000000..b65de747e5 --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_representation_tags.json @@ -0,0 +1,26 @@ +{ + "key": "tags", + "label": "Tags", + "type": "enum", + "multiselection": true, + "enum_items": [ + { + "burnin": "Add burnins" + }, + { + "ftrackreview": "Add to Ftrack" + }, + { + "delete": "Delete output" + }, + { + "slate-frame": "Add slate frame" + }, + { + "no-handles": "Skip handle frames" + }, + { + "sequence": "Output as image sequence" + } + ] +} diff --git a/openpype/settings/entities/schemas/system_schema/host_settings/schema_resolve.json b/openpype/settings/entities/schemas/system_schema/host_settings/schema_resolve.json index ab2b86bf87..8524c92e86 100644 --- a/openpype/settings/entities/schemas/system_schema/host_settings/schema_resolve.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/schema_resolve.json @@ -28,8 +28,8 @@ "name": "template_host_variant", "template_data": [ { - "app_variant_label": "16", - "app_variant": "16" + "app_variant_label": "stable", + "app_variant": "stable" } ] } diff --git a/openpype/settings/entities/schemas/system_schema/host_settings/template_host_variant_items.json b/openpype/settings/entities/schemas/system_schema/host_settings/template_host_variant_items.json index 472840d8fc..ab4d2374a3 100644 --- a/openpype/settings/entities/schemas/system_schema/host_settings/template_host_variant_items.json +++ b/openpype/settings/entities/schemas/system_schema/host_settings/template_host_variant_items.json @@ -10,7 +10,8 @@ "key": "executables", "label": "Executables", "multiplatform": true, - "multipath": true + "multipath": true, + "placeholder": "Executable path" }, { "type":"separator" diff --git a/openpype/tools/mayalookassigner/app.py b/openpype/tools/mayalookassigner/app.py index 09782ea6ac..81aa841eb7 100644 --- a/openpype/tools/mayalookassigner/app.py +++ b/openpype/tools/mayalookassigner/app.py @@ -15,6 +15,8 @@ import maya.api.OpenMaya as om from . import widgets from . import commands +from . vray_proxies import vrayproxy_assign_look + module = sys.modules[__name__] module.window = None @@ -211,9 +213,17 @@ class App(QtWidgets.QWidget): subset_name, asset)) + self.echo("Getting vray proxy nodes ...") + vray_proxies = set(cmds.ls(type="VRayProxy")) + nodes = set(item["nodes"]).difference(vray_proxies) + # Assign look - assign_look_by_version(nodes=item["nodes"], - version_id=version["_id"]) + if nodes: + assign_look_by_version([nodes], version_id=version["_id"]) + + if vray_proxies: + for vp in vray_proxies: + vrayproxy_assign_look(vp, subset_name) end = time.time() diff --git a/openpype/tools/mayalookassigner/commands.py b/openpype/tools/mayalookassigner/commands.py index 98eb3d37b7..2add5d3499 100644 --- a/openpype/tools/mayalookassigner/commands.py +++ b/openpype/tools/mayalookassigner/commands.py @@ -8,6 +8,9 @@ from openpype.hosts.maya.api import lib from avalon import io, api + +import vray_proxies + log = logging.getLogger(__name__) @@ -65,9 +68,7 @@ def get_selected_nodes(): selection = cmds.ls(selection=True, long=True) hierarchy = list_descendents(selection) - nodes = list(set(selection + hierarchy)) - - return nodes + return list(set(selection + hierarchy)) def get_all_asset_nodes(): @@ -132,6 +133,21 @@ def create_items_from_nodes(nodes): asset_view_items = [] id_hashes = create_asset_id_hash(nodes) + + # get ids from alembic + vray_proxy_nodes = cmds.ls(nodes, type="VRayProxy") + for vp in vray_proxy_nodes: + path = cmds.getAttr("{}.fileName".format(vp)) + ids = vray_proxies.get_alembic_ids_cache(path) + parent_id = {} + for k, _ in ids.items(): + pid = k.split(":")[0] + if not parent_id.get(pid): + parent_id.update({pid: [vp]}) + + print("Adding ids from alembic {}".format(path)) + id_hashes.update(parent_id) + if not id_hashes: return asset_view_items @@ -172,7 +188,7 @@ def remove_unused_looks(): host = api.registered_host() - unused = list() + unused = [] for container in host.ls(): if container['loader'] == "LookLoader": members = cmds.sets(container['objectName'], query=True) diff --git a/openpype/tools/mayalookassigner/vray_proxies.py b/openpype/tools/mayalookassigner/vray_proxies.py new file mode 100644 index 0000000000..d2f345e628 --- /dev/null +++ b/openpype/tools/mayalookassigner/vray_proxies.py @@ -0,0 +1,312 @@ +# -*- coding: utf-8 -*- +"""Tools for loading looks to vray proxies.""" +import os +from collections import defaultdict +import logging +import json + +import six + +import alembic.Abc +from maya import cmds + +import avalon.io as io +import avalon.maya +import avalon.api as api + +import openpype.hosts.maya.api.lib as lib + + +log = logging.getLogger(__name__) + + +def get_alembic_paths_by_property(filename, attr, verbose=False): + # type: (str, str, bool) -> dict + """Return attribute value per objects in the Alembic file. + + Reads an Alembic archive hierarchy and retrieves the + value from the `attr` properties on the objects. + + Args: + filename (str): Full path to Alembic archive to read. + attr (str): Id attribute. + verbose (bool): Whether to verbosely log missing attributes. + + Returns: + dict: Mapping of node full path with its id + + """ + # Normalize alembic path + filename = os.path.normpath(filename) + filename = filename.replace("\\", "/") + filename = str(filename) # path must be string + + archive = alembic.Abc.IArchive(filename) + root = archive.getTop() + + iterator = list(root.children) + obj_ids = {} + + for obj in iterator: + name = obj.getFullName() + + # include children for coming iterations + iterator.extend(obj.children) + + props = obj.getProperties() + if props.getNumProperties() == 0: + # Skip those without properties, e.g. '/materials' in a gpuCache + continue + + # THe custom attribute is under the properties' first container under + # the ".arbGeomParams" + prop = props.getProperty(0) # get base property + + _property = None + try: + geo_params = prop.getProperty('.arbGeomParams') + _property = geo_params.getProperty(attr) + except KeyError: + if verbose: + log.debug("Missing attr on: {0}".format(name)) + continue + + if not _property.isConstant(): + log.warning("Id not constant on: {0}".format(name)) + + # Get first value sample + value = _property.getValue()[0] + + obj_ids[name] = value + + return obj_ids + + +def get_alembic_ids_cache(path): + # type: (str) -> dict + """Build a id to node mapping in Alembic file. + + Nodes without IDs are ignored. + + Returns: + dict: Mapping of id to nodes in the Alembic. + + """ + node_ids = get_alembic_paths_by_property(path, attr="cbId") + id_nodes = defaultdict(list) + for node, _id in six.iteritems(node_ids): + id_nodes[_id].append(node) + + return dict(six.iteritems(id_nodes)) + + +def assign_vrayproxy_shaders(vrayproxy, assignments): + # type: (str, dict) -> None + """Assign shaders to content of Vray Proxy. + + This will create shader overrides on Vray Proxy to assign shaders to its + content. + + Todo: + Allow to optimize and assign a single shader to multiple shapes at + once or maybe even set it to the highest available path? + + Args: + vrayproxy (str): Name of Vray Proxy + assignments (dict): Mapping of shader assignments. + + Returns: + None + + """ + # Clear all current shader assignments + plug = vrayproxy + ".shaders" + num = cmds.getAttr(plug, size=True) + for i in reversed(range(num)): + cmds.removeMultiInstance("{}[{}]".format(plug, i), b=True) + + # Create new assignment overrides + index = 0 + for material, paths in assignments.items(): + for path in paths: + plug = "{}.shaders[{}]".format(vrayproxy, index) + cmds.setAttr(plug + ".shadersNames", path, type="string") + cmds.connectAttr(material + ".outColor", + plug + ".shadersConnections", force=True) + index += 1 + + +def get_look_relationships(version_id): + # type: (str) -> dict + """Get relations for the look. + + Args: + version_id (str): Parent version Id. + + Returns: + dict: Dictionary of relations. + + """ + json_representation = io.find_one({"type": "representation", + "parent": version_id, + "name": "json"}) + + # Load relationships + shader_relation = api.get_representation_path(json_representation) + with open(shader_relation, "r") as f: + relationships = json.load(f) + + return relationships + + +def load_look(version_id): + # type: (str) -> list + """Load look from version. + + Get look from version and invoke Loader for it. + + Args: + version_id (str): Version ID + + Returns: + list of shader nodes. + + """ + # Get representations of shader file and relationships + look_representation = io.find_one({"type": "representation", + "parent": version_id, + "name": "ma"}) + + # See if representation is already loaded, if so reuse it. + host = api.registered_host() + representation_id = str(look_representation['_id']) + for container in host.ls(): + if (container['loader'] == "LookLoader" and + container['representation'] == representation_id): + log.info("Reusing loaded look ...") + container_node = container['objectName'] + break + else: + log.info("Using look for the first time ...") + + # Load file + loaders = api.loaders_from_representation(api.discover(api.Loader), + representation_id) + loader = next( + (i for i in loaders if i.__name__ == "LookLoader"), None) + if loader is None: + raise RuntimeError("Could not find LookLoader, this is a bug") + + # Reference the look file + with avalon.maya.maintained_selection(): + container_node = api.load(loader, look_representation) + + # Get container members + shader_nodes = cmds.sets(container_node, query=True) + return shader_nodes + + +def get_latest_version(asset_id, subset): + # type: (str, str) -> dict + """Get latest version of subset. + + Args: + asset_id (str): Asset ID + subset (str): Subset name. + + Returns: + Latest version + + Throws: + RuntimeError: When subset or version doesn't exist. + + """ + subset = io.find_one({"name": subset, + "parent": io.ObjectId(asset_id), + "type": "subset"}) + if not subset: + raise RuntimeError("Subset does not exist: %s" % subset) + + version = io.find_one({"type": "version", + "parent": subset["_id"]}, + sort=[("name", -1)]) + if not version: + raise RuntimeError("Version does not exist.") + + return version + + +def vrayproxy_assign_look(vrayproxy, subset="lookDefault"): + # type: (str, str) -> None + """Assign look to vray proxy. + + Args: + vrayproxy (str): Name of vrayproxy to apply look to. + subset (str): Name of look subset. + + Returns: + None + + """ + path = cmds.getAttr(vrayproxy + ".fileName") + + nodes_by_id = get_alembic_ids_cache(path) + if not nodes_by_id: + log.warning("Alembic file has no cbId attributes: %s" % path) + return + + # Group by asset id so we run over the look per asset + node_ids_by_asset_id = defaultdict(set) + for node_id in nodes_by_id: + asset_id = node_id.split(":", 1)[0] + node_ids_by_asset_id[asset_id].add(node_id) + + for asset_id, node_ids in node_ids_by_asset_id.items(): + + # Get latest look version + try: + version = get_latest_version(asset_id, subset=subset) + except RuntimeError as exc: + print(exc) + continue + + relationships = get_look_relationships(version["_id"]) + shadernodes = load_look(version["_id"]) + + # Get only the node ids and paths related to this asset + # And get the shader edits the look supplies + asset_nodes_by_id = { + node_id: nodes_by_id[node_id] for node_id in node_ids + } + edits = list( + lib.iter_shader_edits( + relationships, shadernodes, asset_nodes_by_id)) + + # Create assignments + assignments = {} + for edit in edits: + if edit["action"] == "assign": + nodes = edit["nodes"] + shader = edit["shader"] + if not cmds.ls(shader, type="shadingEngine"): + print("Skipping non-shader: %s" % shader) + continue + + inputs = cmds.listConnections( + shader + ".surfaceShader", source=True) + if not inputs: + print("Shading engine missing material: %s" % shader) + + # Strip off component assignments + for i, node in enumerate(nodes): + if "." in node: + log.warning( + ("Converting face assignment to full object " + "assignment. This conversion can be lossy: " + "{}").format(node)) + nodes[i] = node.split(".")[0] + + material = inputs[0] + assignments[material] = nodes + + assign_vrayproxy_shaders(vrayproxy, assignments) diff --git a/openpype/tools/mayalookassigner/widgets.py b/openpype/tools/mayalookassigner/widgets.py index bfa8492e69..2dab266af9 100644 --- a/openpype/tools/mayalookassigner/widgets.py +++ b/openpype/tools/mayalookassigner/widgets.py @@ -122,7 +122,7 @@ class AssetOutliner(QtWidgets.QWidget): # Collect the asset item entries per asset # and collect the namespaces we'd like to apply - assets = dict() + assets = {} asset_namespaces = defaultdict(set) for item in items: asset_id = str(item["asset"]["_id"]) diff --git a/openpype/tools/settings/settings/widgets/item_widgets.py b/openpype/tools/settings/settings/widgets/item_widgets.py index 6045b05227..c962219f2b 100644 --- a/openpype/tools/settings/settings/widgets/item_widgets.py +++ b/openpype/tools/settings/settings/widgets/item_widgets.py @@ -631,7 +631,9 @@ class PathWidget(BaseWidget): class PathInputWidget(InputWidget): def _add_inputs_to_layout(self): self.input_field = QtWidgets.QLineEdit(self.content_widget) - self.input_field.setPlaceholderText("Executable path") + placeholder = self.entity.placeholder_text + if placeholder: + self.input_field.setPlaceholderText(placeholder) self.setFocusProxy(self.input_field) self.content_layout.addWidget(self.input_field) diff --git a/openpype/version.py b/openpype/version.py index 6d89c990ed..68f154f5eb 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.0.0-rc1" +__version__ = "3.0.0-rc3" diff --git a/pyproject.toml b/pyproject.toml index 45da304433..42ace686cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "OpenPype" -version = "3.0.0-rc1" +version = "3.0.0-rc3" description = "Open VFX and Animation pipeline with support." authors = ["OpenPype Team "] license = "MIT License" diff --git a/start.py b/start.py index c24e20f63f..baa75aef21 100644 --- a/start.py +++ b/start.py @@ -38,47 +38,47 @@ So, bootstrapping OpenPype looks like this:: .. code-block:: bash -+-------------------------------------------------------+ -| Determine MongoDB connection: | -| Use `OPENPYPE_MONGO`, system keyring `openPypeMongo` | -+--------------------------|----------------------------+ - .--- Found? --. +┌───────────────────────────────────────────────────────┐ +│ Determine MongoDB connection: │ +│ Use `OPENPYPE_MONGO`, system keyring `openPypeMongo` │ +└──────────────────────────┬────────────────────────────┘ + ┌───- Found? -─┐ YES NO - | | - | +------v--------------+ - | | Fire up Igniter GUI |<---------+ - | | and ask User | | - | +---------------------+ | - | | - | | -+-----------------v------------------------------------+ | -| Get location of OpenPype: | | -| 1) Test for `OPENPYPE_PATH` environment variable | | -| 2) Test `openPypePath` in registry setting | | -| 3) Test user data directory | | -| ................................................... | | -| If running from frozen code: | | -| - Use latest one found in user data dir | | -| If running from live code: | | -| - Use live code and install it to user data dir | | -| * can be overridden with `--use-version` argument | | -+-------------------------|----------------------------+ | - .-- Is OpenPype found? --. | - YES NO | - | | | - | +---------------v-----------------+ | - | | Look in `OPENPYPE_PATH`, find | | - | | latest version and install it | | - | | to user data dir. | | - | +--------------|------------------+ | - | .-- Is OpenPype found? --. | - | YES NO --------+ - | | - |<---------+ - | -+-------------v------------+ -| Run OpenPype | -+--------------------------+ + │ │ + │ ┌──────┴──────────────┐ + │ │ Fire up Igniter GUI ├<-────────┐ + │ │ and ask User │ │ + │ └─────────────────────┘ │ + │ │ + │ │ +┌─────────────────┴─────────────────────────────────────┐ │ +│ Get location of OpenPype: │ │ +│ 1) Test for `OPENPYPE_PATH` environment variable │ │ +│ 2) Test `openPypePath` in registry setting │ │ +│ 3) Test user data directory │ │ +│ ····················································· │ │ +│ If running from frozen code: │ │ +│ - Use latest one found in user data dir │ │ +│ If running from live code: │ │ +│ - Use live code and install it to user data dir │ │ +│ * can be overridden with `--use-version` argument │ │ +└──────────────────────────┬────────────────────────────┘ │ + ┌─- Is OpenPype found? -─┐ │ + YES NO │ + │ │ │ + │ ┌─────────────────┴─────────────┐ │ + │ │ Look in `OPENPYPE_PATH`, find │ │ + │ │ latest version and install it │ │ + │ │ to user data dir. │ │ + │ └──────────────┬────────────────┘ │ + │ ┌─- Is OpenPype found? -─┐ │ + │ YES NO -──────┘ + │ │ + ├<-───────┘ + │ +┌─────────────┴────────────┐ +│ Run OpenPype │ +└─────═══════════════──────┘ Todo: @@ -99,6 +99,8 @@ import traceback import subprocess import site from pathlib import Path +import platform + # OPENPYPE_ROOT is variable pointing to build (or code) directory # WARNING `OPENPYPE_ROOT` must be defined before igniter import @@ -110,6 +112,17 @@ if not getattr(sys, 'frozen', False): else: OPENPYPE_ROOT = os.path.dirname(sys.executable) + # FIX #1469: Certificates from certifi are not available in some + # macos builds, so connection to ftrack/mongo will fail with + # unable to verify certificate issuer error. This will add certifi + # certificates so ssl can see them. + # WARNING: this can break stuff if custom certificates are used. In that + # case they need to be merged to certificate bundle and SSL_CERT_FILE + # should point to them. + if not os.getenv("SSL_CERT_FILE") and platform.system().lower() == "darwin": # noqa: E501 + ssl_cert_file = Path(OPENPYPE_ROOT) / "dependencies" / "certifi" / "cacert.pem" # noqa: E501 + os.environ["SSL_CERT_FILE"] = ssl_cert_file.as_posix() + # add dependencies folder to sys.pat for frozen code frozen_libs = os.path.normpath( os.path.join(OPENPYPE_ROOT, "dependencies") diff --git a/tools/build.ps1 b/tools/build.ps1 index 5283ee4754..1fd01191af 100644 --- a/tools/build.ps1 +++ b/tools/build.ps1 @@ -70,8 +70,6 @@ function Install-Poetry() { Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Installing Poetry ... " (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python - - # add it to PATH - $env:PATH = "$($env:PATH);$($env:USERPROFILE)\.poetry\bin" } $art = @" @@ -93,6 +91,14 @@ $current_dir = Get-Location $script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent $openpype_root = (Get-Item $script_dir).parent.FullName +$env:_INSIDE_OPENPYPE_TOOL = "1" + +# make sure Poetry is in PATH +if (-not (Test-Path 'env:POETRY_HOME')) { + $env:POETRY_HOME = "$openpype_root\.poetry" +} +$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" + Set-Location -Path $openpype_root $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py" @@ -126,47 +132,20 @@ Write-Host "Making sure submodules are up-to-date ..." git submodule update --init --recursive Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "Building OpenPype [ " -NoNewline -ForegroundColor white +Write-Host "OpenPype [ " -NoNewline -ForegroundColor white Write-host $openpype_version -NoNewline -ForegroundColor green -Write-Host " ] ..." -ForegroundColor white - -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "Detecting host Python ... " -NoNewline -if (-not (Get-Command "python" -ErrorAction SilentlyContinue)) { - Write-Host "!!! Python not detected" -ForegroundColor red - Exit-WithCode 1 -} -$version_command = @" -import sys -print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1])) -"@ - -$p = & python -c $version_command -$env:PYTHON_VERSION = $p -$m = $p -match '(\d+)\.(\d+)' -if(-not $m) { - Write-Host "!!! Cannot determine version" -ForegroundColor red - Exit-WithCode 1 -} -# We are supporting python 3.6 and up -if(($matches[1] -lt 3) -or ($matches[2] -lt 7)) { - Write-Host "FAILED Version [ $p ] is old and unsupported" -ForegroundColor red - Exit-WithCode 1 -} -Write-Host "OK [ $p ]" -ForegroundColor green - +Write-Host " ]" -ForegroundColor white Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Reading Poetry ... " -NoNewline -if (-not (Test-Path -PathType Container -Path "$($env:USERPROFILE)\.poetry\bin")) { +if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { Write-Host "NOT FOUND" -ForegroundColor Yellow - Install-Poetry - - Write-Host "INSTALLED" -ForegroundColor Cyan + Write-Host "*** " -NoNewline -ForegroundColor Yellow + Write-Host "We need to install Poetry create virtual env first ..." + & "$openpype_root\tools\create_env.ps1" } else { Write-Host "OK" -ForegroundColor Green } -$env:PATH = "$($env:PATH);$($env:USERPROFILE)\.poetry\bin" Write-Host ">>> " -NoNewline -ForegroundColor green Write-Host "Cleaning cache files ... " -NoNewline diff --git a/tools/build.sh b/tools/build.sh index d0593a2b2f..62aecd8ee1 100755 --- a/tools/build.sh +++ b/tools/build.sh @@ -124,7 +124,6 @@ install_poetry () { echo -e "${BIGreen}>>>${RST} Installing Poetry ..." command -v curl >/dev/null 2>&1 || { echo -e "${BIRed}!!!${RST}${BIYellow} Missing ${RST}${BIBlue}curl${BIYellow} command.${RST}"; return 1; } curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python3 - - export PATH="$PATH:$HOME/.poetry/bin" } # Main @@ -141,6 +140,14 @@ main () { version_command="import os;exec(open(os.path.join('$openpype_root', 'openpype', 'version.py')).read());print(__version__);" openpype_version="$(python3 <<< ${version_command})" + _inside_openpype_tool="1" + + # make sure Poetry is in PATH + if [[ -z $POETRY_HOME ]]; then + export POETRY_HOME="$openpype_root/.poetry" + fi + export PATH="$POETRY_HOME/bin:$PATH" + echo -e "${BIYellow}---${RST} Cleaning build directory ..." rm -rf "$openpype_root/build" && mkdir "$openpype_root/build" > /dev/null @@ -149,12 +156,12 @@ main () { clean_pyc echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" - if [ -f "$HOME/.poetry/bin/poetry" ]; then + if [ -f "$POETRY_HOME/bin/poetry" ]; then echo -e "${BIGreen}OK${RST}" - export PATH="$PATH:$HOME/.poetry/bin" else echo -e "${BIYellow}NOT FOUND${RST}" - install_poetry || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } + echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..." + . "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } fi echo -e "${BIGreen}>>>${RST} Making sure submodules are up-to-date ..." diff --git a/tools/create_env.ps1 b/tools/create_env.ps1 index e72e98e04b..c806fc5e49 100644 --- a/tools/create_env.ps1 +++ b/tools/create_env.ps1 @@ -43,9 +43,10 @@ function Show-PSWarning() { function Install-Poetry() { Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Installing Poetry ... " + $env:POETRY_HOME="$openpype_root\.poetry" (Invoke-WebRequest -Uri https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py -UseBasicParsing).Content | python - # add it to PATH - $env:PATH = "$($env:PATH);$($env:USERPROFILE)\.poetry\bin" + $env:PATH = "$($env:PATH);$openpype_root\.poetry\bin" } @@ -84,6 +85,12 @@ $current_dir = Get-Location $script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent $openpype_root = (Get-Item $script_dir).parent.FullName +# make sure Poetry is in PATH +if (-not (Test-Path 'env:POETRY_HOME')) { + $env:POETRY_HOME = "$openpype_root\.poetry" +} +$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" + Set-Location -Path $openpype_root $art = @" @@ -95,8 +102,9 @@ $art = @" https://openpype.io "@ - -Write-Host $art -ForegroundColor DarkGreen +if (-not (Test-Path 'env:_INSIDE_OPENPYPE_TOOL')) { + Write-Host $art -ForegroundColor DarkGreen +} # Enable if PS 7.x is needed. # Show-PSWarning @@ -118,7 +126,7 @@ Test-Python Write-Host ">>> " -NoNewline -ForegroundColor Green Write-Host "Reading Poetry ... " -NoNewline -if (-not (Test-Path -PathType Container -Path "$($env:USERPROFILE)\.poetry\bin")) { +if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { Write-Host "NOT FOUND" -ForegroundColor Yellow Install-Poetry Write-Host "INSTALLED" -ForegroundColor Cyan diff --git a/tools/create_env.sh b/tools/create_env.sh index 04414ddea5..76597bc30e 100755 --- a/tools/create_env.sh +++ b/tools/create_env.sh @@ -136,19 +136,27 @@ realpath () { main () { # Main - echo -e "${BGreen}" - art - echo -e "${RST}" + if [[ -z $_inside_openpype_tool ]]; then + echo -e "${BGreen}" + art + echo -e "${RST}" + fi detect_python || return 1 # Directories openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) + # make sure Poetry is in PATH + if [[ -z $POETRY_HOME ]]; then + export POETRY_HOME="$openpype_root/.poetry" + fi + export PATH="$POETRY_HOME/bin:$PATH" + + pushd "$openpype_root" > /dev/null || return > /dev/null echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" - if [ -f "$HOME/.poetry/bin/poetry" ]; then + if [ -f "$POETRY_HOME/bin/poetry" ]; then echo -e "${BIGreen}OK${RST}" - export PATH="$PATH:$HOME/.poetry/bin" else echo -e "${BIYellow}NOT FOUND${RST}" install_poetry || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } diff --git a/tools/create_zip.ps1 b/tools/create_zip.ps1 index 05db640e5b..4ccfd949fe 100644 --- a/tools/create_zip.ps1 +++ b/tools/create_zip.ps1 @@ -37,6 +37,15 @@ function 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" + +# make sure Poetry is in PATH +if (-not (Test-Path 'env:POETRY_HOME')) { + $env:POETRY_HOME = "$openpype_root\.poetry" +} +$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" + Set-Location -Path $openpype_root $art = @" @@ -63,6 +72,17 @@ if (-not $openpype_version) { Exit-WithCode 1 } +Write-Host ">>> " -NoNewline -ForegroundColor Green +Write-Host "Reading Poetry ... " -NoNewline +if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { + Write-Host "NOT FOUND" -ForegroundColor Yellow + Write-Host "*** " -NoNewline -ForegroundColor Yellow + Write-Host "We need to install Poetry create virtual env first ..." + & "$openpype_root\tools\create_env.ps1" +} else { + Write-Host "OK" -ForegroundColor Green +} + Write-Host ">>> " -NoNewline -ForegroundColor green Write-Host "Cleaning cache files ... " -NoNewline Get-ChildItem $openpype_root -Filter "*.pyc" -Force -Recurse | Remove-Item -Force diff --git a/tools/create_zip.sh b/tools/create_zip.sh index f02eb9987c..030039aa92 100755 --- a/tools/create_zip.sh +++ b/tools/create_zip.sh @@ -120,8 +120,26 @@ main () { # Directories openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) + + _inside_openpype_tool="1" + + # make sure Poetry is in PATH + if [[ -z $POETRY_HOME ]]; then + export POETRY_HOME="$openpype_root/.poetry" + fi + export PATH="$POETRY_HOME/bin:$PATH" + pushd "$openpype_root" > /dev/null || return > /dev/null + echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" + if [ -f "$POETRY_HOME/bin/poetry" ]; then + echo -e "${BIGreen}OK${RST}" + else + echo -e "${BIYellow}NOT FOUND${RST}" + echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..." + . "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } + fi + echo -e "${BIGreen}>>>${RST} Generating zip from current sources ..." PYTHONPATH="$openpype_root:$PYTHONPATH" OPENPYPE_ROOT="$openpype_root" diff --git a/tools/fetch_thirdparty_libs.ps1 b/tools/fetch_thirdparty_libs.ps1 index d1b914fac2..23f0b50c7a 100644 --- a/tools/fetch_thirdparty_libs.ps1 +++ b/tools/fetch_thirdparty_libs.ps1 @@ -14,7 +14,28 @@ PS> .\fetch_thirdparty_libs.ps1 $current_dir = Get-Location $script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent $openpype_root = (Get-Item $script_dir).parent.FullName + +$env:_INSIDE_OPENPYPE_TOOL = "1" + +# make sure Poetry is in PATH +if (-not (Test-Path 'env:POETRY_HOME')) { + $env:POETRY_HOME = "$openpype_root\.poetry" +} +$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" + Set-Location -Path $openpype_root + +Write-Host ">>> " -NoNewline -ForegroundColor Green +Write-Host "Reading Poetry ... " -NoNewline +if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { + Write-Host "NOT FOUND" -ForegroundColor Yellow + Write-Host "*** " -NoNewline -ForegroundColor Yellow + Write-Host "We need to install Poetry create virtual env first ..." + & "$openpype_root\tools\create_env.ps1" +} else { + Write-Host "OK" -ForegroundColor Green +} + & poetry run python "$($openpype_root)\tools\fetch_thirdparty_libs.py" Set-Location -Path $current_dir diff --git a/tools/fetch_thirdparty_libs.sh b/tools/fetch_thirdparty_libs.sh index e305b4b3e4..3875541d57 100755 --- a/tools/fetch_thirdparty_libs.sh +++ b/tools/fetch_thirdparty_libs.sh @@ -116,14 +116,31 @@ main () { echo -e "${BGreen}" art echo -e "${RST}" - detect_python || return 1 # Directories - pype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) - pushd "$pype_root" > /dev/null || return > /dev/null + openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) + + _inside_openpype_tool="1" + + # make sure Poetry is in PATH + if [[ -z $POETRY_HOME ]]; then + export POETRY_HOME="$openpype_root/.poetry" + fi + export PATH="$POETRY_HOME/bin:$PATH" + + echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" + if [ -f "$POETRY_HOME/bin/poetry" ]; then + echo -e "${BIGreen}OK${RST}" + else + echo -e "${BIYellow}NOT FOUND${RST}" + echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..." + . "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } + fi + + pushd "$openpype_root" > /dev/null || return > /dev/null echo -e "${BIGreen}>>>${RST} Running Pype tool ..." - poetry run python3 "$pype_root/tools/fetch_thirdparty_libs.py" + poetry run python3 "$openpype_root/tools/fetch_thirdparty_libs.py" } main \ No newline at end of file diff --git a/tools/make_docs.ps1 b/tools/make_docs.ps1 index aa526bbdc9..f0ccaae004 100644 --- a/tools/make_docs.ps1 +++ b/tools/make_docs.ps1 @@ -16,6 +16,15 @@ PS> .\make_docs.ps1 $current_dir = Get-Location $script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent $openpype_root = (Get-Item $script_dir).parent.FullName + +$env:_INSIDE_OPENPYPE_TOOL = "1" + +# make sure Poetry is in PATH +if (-not (Test-Path 'env:POETRY_HOME')) { + $env:POETRY_HOME = "$openpype_root\.poetry" +} +$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" + Set-Location -Path $openpype_root @@ -32,6 +41,17 @@ $art = @" Write-Host $art -ForegroundColor DarkGreen +Write-Host ">>> " -NoNewline -ForegroundColor Green +Write-Host "Reading Poetry ... " -NoNewline +if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { + Write-Host "NOT FOUND" -ForegroundColor Yellow + Write-Host "*** " -NoNewline -ForegroundColor Yellow + Write-Host "We need to install Poetry create virtual env first ..." + & "$openpype_root\tools\create_env.ps1" +} else { + Write-Host "OK" -ForegroundColor Green +} + Write-Host "This will not overwrite existing source rst files, only scan and add new." Set-Location -Path $openpype_root Write-Host ">>> " -NoNewline -ForegroundColor green diff --git a/tools/make_docs.sh b/tools/make_docs.sh index 2ac12d3d95..edd29a4c6c 100755 --- a/tools/make_docs.sh +++ b/tools/make_docs.sh @@ -71,6 +71,24 @@ main () { # Directories openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) + + _inside_openpype_tool="1" + + # make sure Poetry is in PATH + if [[ -z $POETRY_HOME ]]; then + export POETRY_HOME="$openpype_root/.poetry" + fi + export PATH="$POETRY_HOME/bin:$PATH" + + echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" + if [ -f "$POETRY_HOME/bin/poetry" ]; then + echo -e "${BIGreen}OK${RST}" + else + echo -e "${BIYellow}NOT FOUND${RST}" + echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..." + . "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } + fi + pushd "$openpype_root" > /dev/null || return > /dev/null echo -e "${BIGreen}>>>${RST} Running apidoc ..." diff --git a/tools/run_settings.ps1 b/tools/run_settings.ps1 index 3f99de4b4e..7477e546b3 100644 --- a/tools/run_settings.ps1 +++ b/tools/run_settings.ps1 @@ -14,6 +14,27 @@ PS> .\run_settings.ps1 $current_dir = Get-Location $script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent $openpype_root = (Get-Item $script_dir).parent.FullName + +$env:_INSIDE_OPENPYPE_TOOL = "1" + +# make sure Poetry is in PATH +if (-not (Test-Path 'env:POETRY_HOME')) { + $env:POETRY_HOME = "$openpype_root\.poetry" +} +$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" + Set-Location -Path $openpype_root + +Write-Host ">>> " -NoNewline -ForegroundColor Green +Write-Host "Reading Poetry ... " -NoNewline +if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { + Write-Host "NOT FOUND" -ForegroundColor Yellow + Write-Host "*** " -NoNewline -ForegroundColor Yellow + Write-Host "We need to install Poetry create virtual env first ..." + & "$openpype_root\tools\create_env.ps1" +} else { + Write-Host "OK" -ForegroundColor Green +} + & poetry run python "$($openpype_root)\start.py" settings --dev Set-Location -Path $current_dir \ No newline at end of file diff --git a/tools/run_settings.sh b/tools/run_settings.sh index 0c8a951d7c..2e1dfc744f 100755 --- a/tools/run_settings.sh +++ b/tools/run_settings.sh @@ -50,35 +50,6 @@ BICyan='\033[1;96m' # Cyan BIWhite='\033[1;97m' # White -############################################################################## -# Detect required version of python -# Globals: -# colors -# PYTHON -# Arguments: -# None -# Returns: -# None -############################################################################### -detect_python () { - echo -e "${BIGreen}>>>${RST} Using python \c" - local version_command="import sys;print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))" - local python_version="$(python3 <<< ${version_command})" - oIFS="$IFS" - IFS=. - set -- $python_version - IFS="$oIFS" - if [ "$1" -ge "3" ] && [ "$2" -ge "6" ] ; then - if [ "$2" -gt "7" ] ; then - echo -e "${BIWhite}[${RST} ${BIRed}$1.$2 ${BIWhite}]${RST} - ${BIRed}FAILED${RST} ${BIYellow}Version is new and unsupported, use${RST} ${BIPurple}3.7.x${RST}"; return 1; - else - echo -e "${BIWhite}[${RST} ${BIGreen}$1.$2${RST} ${BIWhite}]${RST}" - fi - else - command -v python3 >/dev/null 2>&1 || { echo -e "${BIRed}$1.$2$ - ${BIRed}FAILED${RST} ${BIYellow}Version is old and unsupported${RST}"; return 1; } - fi -} - ############################################################################## # Clean pyc files in specified directory # Globals: @@ -90,7 +61,7 @@ detect_python () { ############################################################################### clean_pyc () { local path - path=$oepnpype_root + path=$openpype_root echo -e "${BIGreen}>>>${RST} Cleaning pyc at [ ${BIWhite}$path${RST} ] ... \c" find "$path" -regex '^.*\(__pycache__\|\.py[co]\)$' -delete echo -e "${BIGreen}DONE${RST}" @@ -114,12 +85,29 @@ main () { echo -e "${BGreen}" art echo -e "${RST}" - detect_python || return 1 # Directories openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) + + _inside_openpype_tool="1" + + # make sure Poetry is in PATH + if [[ -z $POETRY_HOME ]]; then + export POETRY_HOME="$openpype_root/.poetry" + fi + export PATH="$POETRY_HOME/bin:$PATH" + pushd "$openpype_root" > /dev/null || return > /dev/null + echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" + if [ -f "$POETRY_HOME/bin/poetry" ]; then + echo -e "${BIGreen}OK${RST}" + else + echo -e "${BIYellow}NOT FOUND${RST}" + echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..." + . "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } + fi + echo -e "${BIGreen}>>>${RST} Generating zip from current sources ..." poetry run python3 "$openpype_root/start.py" settings --dev } diff --git a/tools/run_tests.ps1 b/tools/run_tests.ps1 index 5070591c02..15161adabe 100644 --- a/tools/run_tests.ps1 +++ b/tools/run_tests.ps1 @@ -49,6 +49,14 @@ $current_dir = Get-Location $script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent $openpype_root = (Get-Item $script_dir).parent.FullName +$env:_INSIDE_OPENPYPE_TOOL = "1" + +# make sure Poetry is in PATH +if (-not (Test-Path 'env:POETRY_HOME')) { + $env:POETRY_HOME = "$openpype_root\.poetry" +} +$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" + Set-Location -Path $openpype_root $version_file = Get-Content -Path "$($openpype_root)\openpype\version.py" @@ -61,34 +69,20 @@ if (-not $openpype_version) { } Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "Building OpenPype [ " -NoNewline -ForegroundColor white +Write-Host "OpenPype [ " -NoNewline -ForegroundColor white Write-host $openpype_version -NoNewline -ForegroundColor green Write-Host " ] ..." -ForegroundColor white -Write-Host ">>> " -NoNewline -ForegroundColor green -Write-Host "Detecting host Python ... " -NoNewline -if (-not (Get-Command "python" -ErrorAction SilentlyContinue)) { - Write-Host "!!! Python not detected" -ForegroundColor red - Exit-WithCode 1 +Write-Host ">>> " -NoNewline -ForegroundColor Green +Write-Host "Reading Poetry ... " -NoNewline +if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { + Write-Host "NOT FOUND" -ForegroundColor Yellow + Write-Host "*** " -NoNewline -ForegroundColor Yellow + Write-Host "We need to install Poetry create virtual env first ..." + & "$openpype_root\tools\create_env.ps1" +} else { + Write-Host "OK" -ForegroundColor Green } -$version_command = @" -import sys -print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1])) -"@ - -$p = & python -c $version_command -$env:PYTHON_VERSION = $p -$m = $p -match '(\d+)\.(\d+)' -if(-not $m) { - Write-Host "!!! Cannot determine version" -ForegroundColor red - Exit-WithCode 1 -} -# We are supporting python 3.6 and up -if(($matches[1] -lt 3) -or ($matches[2] -lt 7)) { - Write-Host "FAILED Version [ $p ] is old and unsupported" -ForegroundColor red - Exit-WithCode 1 -} -Write-Host "OK [ $p ]" -ForegroundColor green Write-Host ">>> " -NoNewline -ForegroundColor green Write-Host "Cleaning cache files ... " -NoNewline diff --git a/tools/run_tests.sh b/tools/run_tests.sh index 0af052ca01..a161f479b5 100755 --- a/tools/run_tests.sh +++ b/tools/run_tests.sh @@ -49,32 +49,6 @@ BIPurple='\033[1;95m' # Purple BICyan='\033[1;96m' # Cyan BIWhite='\033[1;97m' # White - -############################################################################## -# Detect required version of python -# Globals: -# colors -# PYTHON -# Arguments: -# None -# Returns: -# None -############################################################################### -detect_python () { - echo -e "${BIGreen}>>>${RST} Using python \c" - local version_command="import sys;print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))" - local python_version="$(python3 <<< ${version_command})" - oIFS="$IFS" - IFS=. - set -- $python_version - IFS="$oIFS" - if [ "$1" -ge "3" ] && [ "$2" -ge "6" ] ; then - echo -e "${BIWhite}[${RST} ${BIGreen}$1.$2${RST} ${BIWhite}]${RST}" - else - command -v python3 >/dev/null 2>&1 || { echo -e "${BIRed}FAILED${RST} ${BIYellow} Version [${RST}${BICyan}$1.$2${RST}]${BIYellow} is old and unsupported${RST}"; return 1; } - fi -} - ############################################################################## # Clean pyc files in specified directory # Globals: @@ -110,10 +84,27 @@ main () { echo -e "${BGreen}" art echo -e "${RST}" - detect_python || return 1 # Directories openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) + + _inside_openpype_tool="1" + + # make sure Poetry is in PATH + if [[ -z $POETRY_HOME ]]; then + export POETRY_HOME="$openpype_root/.poetry" + fi + export PATH="$POETRY_HOME/bin:$PATH" + + echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" + if [ -f "$POETRY_HOME/bin/poetry" ]; then + echo -e "${BIGreen}OK${RST}" + else + echo -e "${BIYellow}NOT FOUND${RST}" + echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..." + . "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } + fi + pushd "$openpype_root" || return > /dev/null echo -e "${BIGreen}>>>${RST} Testing OpenPype ..." diff --git a/tools/run_tray.ps1 b/tools/run_tray.ps1 index 9485584c6f..533a791836 100644 --- a/tools/run_tray.ps1 +++ b/tools/run_tray.ps1 @@ -13,7 +13,27 @@ PS> .\run_tray.ps1 $current_dir = Get-Location $script_dir = Split-Path -Path $MyInvocation.MyCommand.Definition -Parent $openpype_root = (Get-Item $script_dir).parent.FullName + +$env:_INSIDE_OPENPYPE_TOOL = "1" + +# make sure Poetry is in PATH +if (-not (Test-Path 'env:POETRY_HOME')) { + $env:POETRY_HOME = "$openpype_root\.poetry" +} +$env:PATH = "$($env:PATH);$($env:POETRY_HOME)\bin" + Set-Location -Path $openpype_root +Write-Host ">>> " -NoNewline -ForegroundColor Green +Write-Host "Reading Poetry ... " -NoNewline +if (-not (Test-Path -PathType Container -Path "$openpype_root\.poetry\bin")) { + Write-Host "NOT FOUND" -ForegroundColor Yellow + Write-Host "*** " -NoNewline -ForegroundColor Yellow + Write-Host "We need to install Poetry create virtual env first ..." + & "$openpype_root\tools\create_env.ps1" +} else { + Write-Host "OK" -ForegroundColor Green +} + & poetry run python "$($openpype_root)\start.py" tray --debug Set-Location -Path $current_dir \ No newline at end of file diff --git a/tools/run_tray.sh b/tools/run_tray.sh index 8174f7e38a..7471d3ca5a 100755 --- a/tools/run_tray.sh +++ b/tools/run_tray.sh @@ -49,37 +49,6 @@ BIPurple='\033[1;95m' # Purple BICyan='\033[1;96m' # Cyan BIWhite='\033[1;97m' # White - -############################################################################## -# Detect required version of python -# Globals: -# colors -# PYTHON -# Arguments: -# None -# Returns: -# None -############################################################################### -detect_python () { - echo -e "${BIGreen}>>>${RST} Using python \c" - local version_command="import sys;print('{0}.{1}'.format(sys.version_info[0], sys.version_info[1]))" - local python_version="$(python3 <<< ${version_command})" - oIFS="$IFS" - IFS=. - set -- $python_version - IFS="$oIFS" - if [ "$1" -ge "3" ] && [ "$2" -ge "6" ] ; then - if [ "$2" -gt "7" ] ; then - echo -e "${BIWhite}[${RST} ${BIRed}$1.$2 ${BIWhite}]${RST} - ${BIRed}FAILED${RST} ${BIYellow}Version is new and unsupported, use${RST} ${BIPurple}3.7.x${RST}"; return 1; - else - echo -e "${BIWhite}[${RST} ${BIGreen}$1.$2${RST} ${BIWhite}]${RST}" - fi - PYTHON="python3" - else - command -v python3 >/dev/null 2>&1 || { echo -e "${BIRed}$1.$2$ - ${BIRed}FAILED${RST} ${BIYellow}Version is old and unsupported${RST}"; return 1; } - fi -} - ############################################################################## # Clean pyc files in specified directory # Globals: @@ -119,6 +88,24 @@ main () { # Directories openpype_root=$(realpath $(dirname $(dirname "${BASH_SOURCE[0]}"))) + + _inside_openpype_tool="1" + + # make sure Poetry is in PATH + if [[ -z $POETRY_HOME ]]; then + export POETRY_HOME="$openpype_root/.poetry" + fi + export PATH="$POETRY_HOME/bin:$PATH" + + echo -e "${BIGreen}>>>${RST} Reading Poetry ... \c" + if [ -f "$POETRY_HOME/bin/poetry" ]; then + echo -e "${BIGreen}OK${RST}" + else + echo -e "${BIYellow}NOT FOUND${RST}" + echo -e "${BIYellow}***${RST} We need to install Poetry and virtual env ..." + . "$openpype_root/tools/create_env.sh" || { echo -e "${BIRed}!!!${RST} Poetry installation failed"; return; } + fi + pushd "$openpype_root" > /dev/null || return > /dev/null echo -e "${BIGreen}>>>${RST} Running OpenPype Tray with debug option ..." diff --git a/website/docs/artist_hosts_maya.md b/website/docs/artist_hosts_maya.md index d19bde7b49..fc94f20f02 100644 --- a/website/docs/artist_hosts_maya.md +++ b/website/docs/artist_hosts_maya.md @@ -715,3 +715,40 @@ Once data are marked as Redshift Proxy instance, they can be published - **OpenP Published proxy files can be loaded with OpenPype Loader. It will create mesh and attach Redshift Proxy parameters to it - Redshift will then represent proxy with bounding box. + +## Using VRay Proxies + +OpenPype support publishing, loading and using of VRay Proxy in look management. Their underlaying format +can be either vrmesh or alembic. + +:::warning vrmesh or alembic and look management +Be aware that **vrmesh** cannot be used with looks as it doesn't retain IDs necessary to map shaders to geometry. +::: + +### Creating VRay Proxy + +To create VRay Proxy, select geometry you want and - **OpenPype → Create ...** select **VRay Proxy**. Name your +subset as you want and press **Create** button. + +This will create `vrayproxy` set for your subset. You can set some options in Attribute editor, mainly if you want +export animation instead of single frame. + +![Maya - VRay Proxy Creation](assets/maya-vray_proxy.jpg) + +### Publishing VRay Proxies + +VRay Proxy can be published - **OpenPype → Publish ...**. It will publish data as VRays `vrmesh` format and as +Alembic file. + +## Using VRay Proxies + +You can load VRay Proxy using loader - **OpenPype → Loader ...** + +![Maya - VRay Proxy Creation](assets/maya-vray_proxy-loader.jpg) + +Select your subset and right-click. Select **Import VRay Proxy (vrmesh)** to import it. + +:::note +Note that even if it states `vrmesh` in descriptions, if loader finds Alembic published along (default behavior) it will +use abc file instead of vrmesh as it is more flexible and without it looks doesn't work. +::: diff --git a/website/docs/assets/maya-vray_proxy-loader.jpg b/website/docs/assets/maya-vray_proxy-loader.jpg new file mode 100644 index 0000000000..29632796ea Binary files /dev/null and b/website/docs/assets/maya-vray_proxy-loader.jpg differ diff --git a/website/docs/assets/maya-vray_proxy.jpg b/website/docs/assets/maya-vray_proxy.jpg new file mode 100644 index 0000000000..181b77db89 Binary files /dev/null and b/website/docs/assets/maya-vray_proxy.jpg differ