From 0aeded448613e0faddbeb09a711456dada9e0826 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 8 Aug 2022 16:04:31 +0200 Subject: [PATCH 001/144] Add collectors for input links for Maya + Fusion --- .../fusion/plugins/publish/collect_inputs.py | 112 +++++++++ openpype/hosts/maya/api/lib_rendersetup.py | 68 ++++++ .../maya/plugins/publish/collect_inputs.py | 214 ++++++++++++++++++ 3 files changed, 394 insertions(+) create mode 100644 openpype/hosts/fusion/plugins/publish/collect_inputs.py create mode 100644 openpype/hosts/maya/plugins/publish/collect_inputs.py diff --git a/openpype/hosts/fusion/plugins/publish/collect_inputs.py b/openpype/hosts/fusion/plugins/publish/collect_inputs.py new file mode 100644 index 0000000000..e610575e3a --- /dev/null +++ b/openpype/hosts/fusion/plugins/publish/collect_inputs.py @@ -0,0 +1,112 @@ +import pyblish.api + +from openpype.pipeline import registered_host + + +def collect_input_containers(tools): + """Collect containers that contain any of the node in `nodes`. + + This will return any loaded Avalon container that contains at least one of + the nodes. As such, the Avalon container is an input for it. Or in short, + there are member nodes of that container. + + Returns: + list: Input avalon containers + + """ + + # Lookup by node ids + lookup = frozenset([tool.Name for tool in tools]) + + containers = [] + host = registered_host() + for container in host.ls(): + + name = container["_tool"].Name + + # We currently assume no "groups" as containers but just single tools + # like a single "Loader" operator. As such we just check whether the + # Loader is part of the processing queue. + if name in lookup: + containers.append(container) + + return containers + + +def iter_upstream(tool): + """Yields all upstream inputs for the current tool. + + Yields: + tool: The input tools. + + """ + + def get_connected_input_tools(tool): + """Helper function that returns connected input tools for a tool.""" + inputs = [] + + # Filter only to actual types that will have sensible upstream + # connections. So we ignore just "Number" inputs as they can be + # many to iterate, slowing things down quite a bit - and in practice + # they don't have upstream connections. + VALID_INPUT_TYPES = ['Image', 'Particles', 'Mask', 'DataType3D'] + for type_ in VALID_INPUT_TYPES: + for input_ in tool.GetInputList(type_).values(): + output = input_.GetConnectedOutput() + if output: + input_tool = output.GetTool() + inputs.append(input_tool) + + return inputs + + # Initialize process queue with the node's inputs itself + queue = get_connected_input_tools(tool) + + # We keep track of which node names we have processed so far, to ensure we + # don't process the same hierarchy again. We are not pushing the tool + # itself into the set as that doesn't correctly recognize the same tool. + # Since tool names are unique in a comp in Fusion we rely on that. + collected = set(tool.Name for tool in queue) + + # Traverse upstream references for all nodes and yield them as we + # process the queue. + while queue: + upstream_tool = queue.pop() + yield upstream_tool + + # Find upstream tools that are not collected yet. + upstream_inputs = get_connected_input_tools(upstream_tool) + upstream_inputs = [t for t in upstream_inputs if + t.Name not in collected] + + queue.extend(upstream_inputs) + collected.update(tool.Name for tool in upstream_inputs) + + +class CollectUpstreamInputs(pyblish.api.InstancePlugin): + """Collect source input containers used for this publish. + + This will include `inputs` data of which loaded publishes were used in the + generation of this publish. This leaves an upstream trace to what was used + as input. + + """ + + label = "Collect Inputs" + order = pyblish.api.CollectorOrder + 0.2 + hosts = ["fusion"] + + def process(self, instance): + + # Get all upstream and include itself + tool = instance[0] + nodes = list(iter_upstream(tool)) + nodes.append(tool) + + # Collect containers for the given set of nodes + containers = collect_input_containers(nodes) + + inputs = [c["representation"] for c in containers] + instance.data["inputs"] = inputs + + self.log.info("Collected inputs: %s" % inputs) diff --git a/openpype/hosts/maya/api/lib_rendersetup.py b/openpype/hosts/maya/api/lib_rendersetup.py index 0fdc54a068..a00be52d8e 100644 --- a/openpype/hosts/maya/api/lib_rendersetup.py +++ b/openpype/hosts/maya/api/lib_rendersetup.py @@ -348,3 +348,71 @@ def get_attr_overrides(node_attr, layer, break return reversed(plug_overrides) + + +def get_shader_in_layer(node, layer): + """Return the assigned shader in a renderlayer without switching layers. + + This has been developed and tested for Legacy Renderlayers and *not* for + Render Setup. + + Note: This will also return the shader for any face assignments, however + it will *not* return the components they are assigned to. This could + be implemented, but since Maya's renderlayers are famous for breaking + with face assignments there has been no need for this function to + support that. + + Returns: + list: The list of assigned shaders in the given layer. + + """ + + def _get_connected_shader(shape): + """Return current shader""" + return cmds.listConnections(shape + ".instObjGroups", + source=False, + destination=True, + plugs=False, + connections=False, + type="shadingEngine") or [] + + # We check the instObjGroups (shader connection) for layer overrides. + plug = node + ".instObjGroups" + + # Ignore complex query if we're in the layer anyway (optimization) + current_layer = cmds.editRenderLayerGlobals(query=True, + currentRenderLayer=True) + if layer == current_layer: + return _get_connected_shader(plug) + + connections = cmds.listConnections(plug, + plugs=True, + source=False, + destination=True, + type="renderLayer") or [] + connections = filter(lambda x: x.endswith(".outPlug"), connections) + if not connections: + # If no overrides anywhere on the shader, just get the current shader + return _get_connected_shader(plug) + + def _get_override(connections, layer): + """Return the overridden connection for that layer in connections""" + # If there's an override on that layer, return that. + for connection in connections: + if (connection.startswith(layer + ".outAdjustments") and + connection.endswith(".outPlug")): + + # This is a shader override on that layer so get the shader + # connected to .outValue of the .outAdjustment[i] + out_adjustment = connection.rsplit(".", 1)[0] + connection_attr = out_adjustment + ".outValue" + override = cmds.listConnections(connection_attr) or [] + + return override + + override_shader = _get_override(connections, layer) + if override_shader is not None: + return override_shader + else: + # Get the override for "defaultRenderLayer" (=masterLayer) + return _get_override(connections, layer="defaultRenderLayer") diff --git a/openpype/hosts/maya/plugins/publish/collect_inputs.py b/openpype/hosts/maya/plugins/publish/collect_inputs.py new file mode 100644 index 0000000000..8afa1e4757 --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/collect_inputs.py @@ -0,0 +1,214 @@ +import copy +from maya import cmds +import maya.api.OpenMaya as om +import pyblish.api + +from openpype.pipeline import registered_host +from openpype.hosts.maya.api.lib import get_container_members +from openpype.hosts.maya.api.lib_rendersetup import get_shader_in_layer + + +def iter_history(nodes, + filter=om.MFn.kInvalid, + direction=om.MItDependencyGraph.kUpstream): + """Iterate unique upstream history for list of nodes. + + This acts as a replacement to maya.cmds.listHistory. + It's faster by about 2x-3x. It returns less than + maya.cmds.listHistory as it excludes the input nodes + from the output (unless an input node was history + for another input node). It also excludes duplicates. + + Args: + nodes (list): Maya node names to start search from. + filter (om.MFn.Type): Filter to only specific types. + e.g. to dag nodes using om.MFn.kDagNode + direction (om.MItDependencyGraph.Direction): Direction to traverse in. + Defaults to upstream. + + Yields: + str: Node names in upstream history. + + """ + if not nodes: + return + + sel = om.MSelectionList() + for node in nodes: + sel.add(node) + + it = om.MItDependencyGraph(sel.getDependNode(0)) # init iterator + handle = om.MObjectHandle + + traversed = set() + fn_dep = om.MFnDependencyNode() + fn_dag = om.MFnDagNode() + for i in range(sel.length()): + + start_node = sel.getDependNode(i) + start_node_hash = handle(start_node).hashCode() + if start_node_hash in traversed: + continue + + it.resetTo(start_node, + filter=filter, + direction=direction) + while not it.isDone(): + + node = it.currentNode() + node_hash = handle(node).hashCode() + + if node_hash in traversed: + it.prune() + it.next() + continue + + traversed.add(node_hash) + + if node.hasFn(om.MFn.kDagNode): + fn_dag.setObject(node) + yield fn_dag.fullPathName() + else: + fn_dep.setObject(node) + yield fn_dep.name() + + it.next() + + +def collect_input_containers(containers, nodes): + """Collect containers that contain any of the node in `nodes`. + + This will return any loaded Avalon container that contains at least one of + the nodes. As such, the Avalon container is an input for it. Or in short, + there are member nodes of that container. + + Returns: + list: Input avalon containers + + """ + # Assume the containers have collected their cached '_members' data + # in the collector. + return [container for container in containers + if any(node in container["_members"] for node in nodes)] + + +class CollectUpstreamInputs(pyblish.api.InstancePlugin): + """Collect input source inputs for this publish. + + This will include `inputs` data of which loaded publishes were used in the + generation of this publish. This leaves an upstream trace to what was used + as input. + + """ + + label = "Collect Inputs" + order = pyblish.api.CollectorOrder + 0.34 + hosts = ["maya"] + + def process(self, instance): + + # For large scenes the querying of "host.ls()" can be relatively slow + # e.g. up to a second. Many instances calling it easily slows this + # down. As such, we cache it so we trigger it only once. + # todo: Instead of hidden cache make "CollectContainers" plug-in + cache_key = "__cache_containers" + scene_containers = instance.context.data.get(cache_key, None) + if scene_containers is None: + # Query the scenes' containers if there's no cache yet + host = registered_host() + scene_containers = list(host.ls()) + for container in scene_containers: + # Embed the members into the container dictionary + container_members = set(get_container_members(container)) + container["_members"] = container_members + instance.context.data["__cache_containers"] = scene_containers + + # Collect the relevant input containers for this instance + if "renderlayer" in set(instance.data.get("families", [])): + # Special behavior for renderlayers + self.log.debug("Collecting renderlayer inputs....") + containers = self._collect_renderlayer_inputs(scene_containers, + instance) + + else: + # Basic behavior + nodes = instance[:] + + # Include any input connections of history with long names + # For optimization purposes only trace upstream from shape nodes + # looking for used dag nodes. This way having just a constraint + # on a transform is also ignored which tended to give irrelevant + # inputs for the majority of our use cases. We tend to care more + # about geometry inputs. + shapes = cmds.ls(nodes, + type=("mesh", "nurbsSurface", "nurbsCurve"), + noIntermediate=True) + if shapes: + history = list(iter_history(shapes, filter=om.MFn.kShape)) + history = cmds.ls(history, long=True) + + # Include the transforms in the collected history as shapes + # are excluded from containers + transforms = cmds.listRelatives(cmds.ls(history, shapes=True), + parent=True, + fullPath=True, + type="transform") + if transforms: + history.extend(transforms) + + if history: + nodes = list(set(nodes + history)) + + # Collect containers for the given set of nodes + containers = collect_input_containers(scene_containers, + nodes) + + inputs = [c["representation"] for c in containers] + instance.data["inputs"] = inputs + + self.log.info("Collected inputs: %s" % inputs) + + def _collect_renderlayer_inputs(self, scene_containers, instance): + """Collects inputs from nodes in renderlayer, incl. shaders + camera""" + + # Get the renderlayer + renderlayer = instance.data.get("setMembers") + + if renderlayer == "defaultRenderLayer": + # Assume all loaded containers in the scene are inputs + # for the masterlayer + return copy.deepcopy(scene_containers) + else: + # Get the members of the layer + members = cmds.editRenderLayerMembers(renderlayer, + query=True, + fullNames=True) or [] + + # In some cases invalid objects are returned from + # `editRenderLayerMembers` so we filter them out + members = cmds.ls(members, long=True) + + # Include all children + children = cmds.listRelatives(members, + allDescendents=True, + fullPath=True) or [] + members.extend(children) + + # Include assigned shaders in renderlayer + shapes = cmds.ls(members, shapes=True, long=True) + shaders = set() + for shape in shapes: + shape_shaders = get_shader_in_layer(shape, layer=renderlayer) + if not shape_shaders: + continue + shaders.update(shape_shaders) + members.extend(shaders) + + # Explicitly include the camera being rendered in renderlayer + cameras = instance.data.get("cameras") + members.extend(cameras) + + containers = collect_input_containers(scene_containers, members) + + return containers + From d92c6eac115d0e857738c5944abe11fc2c840f1e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 8 Aug 2022 16:58:33 +0200 Subject: [PATCH 002/144] Remove blank line --- openpype/hosts/maya/plugins/publish/collect_inputs.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_inputs.py b/openpype/hosts/maya/plugins/publish/collect_inputs.py index 8afa1e4757..d34f289e05 100644 --- a/openpype/hosts/maya/plugins/publish/collect_inputs.py +++ b/openpype/hosts/maya/plugins/publish/collect_inputs.py @@ -211,4 +211,3 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin): containers = collect_input_containers(scene_containers, members) return containers - From 4721a683094acfc2fd709d44c83d3907d91a2aa2 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 8 Aug 2022 17:00:17 +0200 Subject: [PATCH 003/144] Shush the hound - code is correct --- openpype/hosts/maya/plugins/publish/collect_inputs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_inputs.py b/openpype/hosts/maya/plugins/publish/collect_inputs.py index d34f289e05..43941bde4f 100644 --- a/openpype/hosts/maya/plugins/publish/collect_inputs.py +++ b/openpype/hosts/maya/plugins/publish/collect_inputs.py @@ -60,7 +60,7 @@ def iter_history(nodes, if node_hash in traversed: it.prune() - it.next() + it.next() # noqa: B305 continue traversed.add(node_hash) @@ -72,7 +72,7 @@ def iter_history(nodes, fn_dep.setObject(node) yield fn_dep.name() - it.next() + it.next() # noqa: B305 def collect_input_containers(containers, nodes): From 4a4bb22f60353bb9cfcdb64c516b2b145cb7c966 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Aug 2022 11:36:36 +0200 Subject: [PATCH 004/144] Refactor collect inputs as `inputRepresentations` --- openpype/hosts/fusion/plugins/publish/collect_inputs.py | 6 ++++-- openpype/hosts/houdini/plugins/publish/collect_inputs.py | 6 ++++-- openpype/hosts/maya/plugins/publish/collect_inputs.py | 6 ++++-- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/fusion/plugins/publish/collect_inputs.py b/openpype/hosts/fusion/plugins/publish/collect_inputs.py index e610575e3a..8f9857b02f 100644 --- a/openpype/hosts/fusion/plugins/publish/collect_inputs.py +++ b/openpype/hosts/fusion/plugins/publish/collect_inputs.py @@ -1,3 +1,5 @@ +from bson.objectid import ObjectId + import pyblish.api from openpype.pipeline import registered_host @@ -106,7 +108,7 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin): # Collect containers for the given set of nodes containers = collect_input_containers(nodes) - inputs = [c["representation"] for c in containers] - instance.data["inputs"] = inputs + inputs = [ObjectId(c["representation"]) for c in containers] + instance.data["inputRepresentations"] = inputs self.log.info("Collected inputs: %s" % inputs) diff --git a/openpype/hosts/houdini/plugins/publish/collect_inputs.py b/openpype/hosts/houdini/plugins/publish/collect_inputs.py index 8c7098c710..9ee0248bd9 100644 --- a/openpype/hosts/houdini/plugins/publish/collect_inputs.py +++ b/openpype/hosts/houdini/plugins/publish/collect_inputs.py @@ -1,3 +1,5 @@ +from bson.objectid import ObjectId + import pyblish.api from openpype.pipeline import registered_host @@ -115,7 +117,7 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin): # Collect containers for the given set of nodes containers = collect_input_containers(nodes) - inputs = [c["representation"] for c in containers] - instance.data["inputs"] = inputs + inputs = [ObjectId(c["representation"]) for c in containers] + instance.data["inputRepresentations"] = inputs self.log.info("Collected inputs: %s" % inputs) diff --git a/openpype/hosts/maya/plugins/publish/collect_inputs.py b/openpype/hosts/maya/plugins/publish/collect_inputs.py index 43941bde4f..470fceffc9 100644 --- a/openpype/hosts/maya/plugins/publish/collect_inputs.py +++ b/openpype/hosts/maya/plugins/publish/collect_inputs.py @@ -1,4 +1,6 @@ import copy +from bson.objectid import ObjectId + from maya import cmds import maya.api.OpenMaya as om import pyblish.api @@ -163,8 +165,8 @@ class CollectUpstreamInputs(pyblish.api.InstancePlugin): containers = collect_input_containers(scene_containers, nodes) - inputs = [c["representation"] for c in containers] - instance.data["inputs"] = inputs + inputs = [ObjectId(c["representation"]) for c in containers] + instance.data["inputRepresentations"] = inputs self.log.info("Collected inputs: %s" % inputs) From 19f81dbf40bbe509506f9f13b4dcfa70133f8b92 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Aug 2022 11:38:44 +0200 Subject: [PATCH 005/144] Add Collector to convert `inputRepresentations` -> `inputVersions` --- ...llect_input_representations_to_versions.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 openpype/plugins/publish/collect_input_representations_to_versions.py diff --git a/openpype/plugins/publish/collect_input_representations_to_versions.py b/openpype/plugins/publish/collect_input_representations_to_versions.py new file mode 100644 index 0000000000..03f2abf51f --- /dev/null +++ b/openpype/plugins/publish/collect_input_representations_to_versions.py @@ -0,0 +1,48 @@ +import pyblish.api + +from bson.objectid import ObjectId + +from openpype.client import get_representations + + +class CollectInputRepresentationsToVersions(pyblish.api.ContextPlugin): + """Converts collected input representations to input versions. + + Any data in `instance.data["inputRepresentations"]` gets converted into + `instance.data["inputVersions"]` as supported in OpenPype v3. + + """ + # This is a ContextPlugin because then we can query the database only once + # for the conversion of representation ids to version ids (optimization) + label = "Input Representations to Versions" + order = pyblish.api.CollectorOrder + 0.499 + hosts = ["*"] + + def process(self, context): + # Query all version ids for representation ids from the database once + representations = set() + for instance in context: + inst_repre = instance.data.get("inputRepresentations", []) + representations.update(inst_repre) + + representations_docs = get_representations( + project_name=context.data["projectEntity"]["name"], + representation_ids=representations, + fields=["_id", "parent"]) + + representation_id_to_version_id = { + repre["_id"]: repre["parent"] for repre in representations_docs + } + + for instance in context: + inst_repre = instance.data.get("inputRepresentations", []) + if not inst_repre: + continue + + input_versions = instance.data.get("inputVersions", []) + for repre_id in inst_repre: + repre_id = ObjectId(repre_id) + version_id = representation_id_to_version_id[repre_id] + input_versions.append(version_id) + instance.data["inputVersions"] = input_versions + From 257f027d900e259d611bc70becaa1a30065ee3fd Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 9 Aug 2022 11:40:28 +0200 Subject: [PATCH 006/144] Remove blank line --- .../plugins/publish/collect_input_representations_to_versions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/plugins/publish/collect_input_representations_to_versions.py b/openpype/plugins/publish/collect_input_representations_to_versions.py index 03f2abf51f..18a19bce80 100644 --- a/openpype/plugins/publish/collect_input_representations_to_versions.py +++ b/openpype/plugins/publish/collect_input_representations_to_versions.py @@ -45,4 +45,3 @@ class CollectInputRepresentationsToVersions(pyblish.api.ContextPlugin): version_id = representation_id_to_version_id[repre_id] input_versions.append(version_id) instance.data["inputVersions"] = input_versions - From 5b559fd28d439a2e9ba2185eae428b7c63b69fb5 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 9 Aug 2022 13:17:06 +0200 Subject: [PATCH 007/144] create shelf manager definition for houdini in openpype project settings --- .../defaults/project_settings/houdini.json | 21 +++++ .../schema_project_houdini.json | 6 +- .../schemas/schema_houdini_scriptshelf.json | 81 +++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 911bf82d9b..5805f600c5 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -1,4 +1,25 @@ { + "shelves": [ + { + "shelf_set_name": "OpenPype Shelves", + "shelf_set_source_path": "/path/to/your/shelf_set_file", + "shelf_definition": [ + { + "shelf_name": "OpenPype Shelf", + "shelf_file_path": "/path/to/your/shelf_file", + "tools_list": [ + { + "name": "OpenPype Tool", + "filepath": "/path/to/your/tool_file", + "script": "/path/to/your/tool_script", + "icon": "/path/to/your/icon", + "help": "Help message for your tool" + } + ] + } + ] + } + ], "create": { "CreateArnoldAss": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json index cad99dde22..bde4352964 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_houdini.json @@ -5,6 +5,10 @@ "label": "Houdini", "is_file": true, "children": [ + { + "type": "schema", + "name": "schema_houdini_scriptshelf" + }, { "type": "schema", "name": "schema_houdini_create" @@ -28,4 +32,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json new file mode 100644 index 0000000000..5a84c6d5cc --- /dev/null +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json @@ -0,0 +1,81 @@ +{ + "type": "list", + "key": "shelves", + "label": "Shelves Manager", + "is_group": true, + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "text", + "key": "shelf_set_name", + "label": "Shelf Set Name" + }, + { + "type": "path", + "key": "shelf_set_source_path", + "label": "Shelf Set Path", + "multipath": true, + "multiplatform": true + }, + { + "type": "list", + "key": "shelf_definition", + "label": "Shelves", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "text", + "key": "shelf_name", + "label": "Shelf Name" + }, + { + "type": "text", + "key": "shelf_file_path", + "label": "Shelf File Path" + }, + { + "type": "list", + "key": "tools_list", + "label": "Tools", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "text", + "key": "name", + "label": "Name" + }, + { + "type": "text", + "key": "filepath", + "label": "File Path" + }, + { + "type": "text", + "key": "script", + "label": "Script" + }, + { + "type": "text", + "key": "icon", + "label": "Icon" + }, + { + "type": "text", + "key": "help", + "label": "Help" + } + ] + } + } + ] + } + } + ] + } +} \ No newline at end of file From a006b5df63bb0b3f3935f0873a2f4537966ffddb Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 9 Aug 2022 15:43:47 +0200 Subject: [PATCH 008/144] set up the shelf creation in the _set_context_settings function --- openpype/hosts/houdini/api/lib.py | 32 ++++++++++++++++++++++++++ openpype/hosts/houdini/api/pipeline.py | 2 ++ 2 files changed, 34 insertions(+) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index c8a7f92bb9..55832abeb3 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -460,3 +460,35 @@ def reset_framerange(): hou.playbar.setFrameRange(frame_start, frame_end) hou.playbar.setPlaybackRange(frame_start, frame_end) hou.setFrame(frame_start) + + +def create_shelf(): + hou.shelves.beginChangeBlock() + + custom_shelf = hou.shelves.newShelf( + file_path='', + name="custom_shelf", + label="Custom Shelf" + ) + + new_tool = hou.shelves.newTool( + file_path='', + name='new_tool', + label='New Tool', + script='', + language=hou.scriptLanguage.Python, + icon='', + help='This is a new tool' + ) + + if new_tool not in custom_shelf.tools(): + custom_shelf.setTools(list(custom_shelf.tools()) + [new_tool]) + + shelf_set = [ + shelf for shelf in hou.shelves.shelfSets().values() + if shelf.label() == "Create and Refine" + ][0] + + shelf_set.setShelves(shelf_set.shelves() + (custom_shelf,)) + + hou.shelves.endChangeBlock() diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index b5f5459392..2f414020c4 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -309,6 +309,7 @@ def _set_context_settings(): fps resolution renderer + shelves Returns: None @@ -320,6 +321,7 @@ def _set_context_settings(): lib.set_scene_fps(fps) lib.reset_framerange() + lib.create_shelf() def on_pyblish_instance_toggled(instance, new_value, old_value): From cdd90ad2a79de9ba0c2000a00eff65efcde30a8d Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 9 Aug 2022 17:13:08 +0200 Subject: [PATCH 009/144] main structure to generate shelves --- openpype/hosts/houdini/api/pipeline.py | 4 +-- openpype/hosts/houdini/api/shelves.py | 47 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 openpype/hosts/houdini/api/shelves.py diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index 2f414020c4..f809f0ce56 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -14,7 +14,7 @@ from openpype.pipeline import ( ) from openpype.pipeline.load import any_outdated_containers import openpype.hosts.houdini -from openpype.hosts.houdini.api import lib +from openpype.hosts.houdini.api import lib, shelves from openpype.lib import ( register_event_callback, @@ -74,6 +74,7 @@ def install(): # so it initializes into the correct scene FPS, Frame Range, etc. # todo: make sure this doesn't trigger when opening with last workfile _set_context_settings() + shelves.generate_shelves() def uninstall(): @@ -321,7 +322,6 @@ def _set_context_settings(): lib.set_scene_fps(fps) lib.reset_framerange() - lib.create_shelf() def on_pyblish_instance_toggled(instance, new_value, old_value): diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py new file mode 100644 index 0000000000..b8f6419175 --- /dev/null +++ b/openpype/hosts/houdini/api/shelves.py @@ -0,0 +1,47 @@ +import os +import logging + +from openpype.settings import get_project_settings + +log = logging.getLogger(__name__) + + +def generate_shelves(): + # load configuration of custom menu + project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) + shelves_set_config = project_settings["houdini"]["shelves"] + + if not shelves_set_config: + log.warning("No custom shelves found.") + return + + # run the shelf generator for Houdini + for shelf_set in shelves_set_config: + pass + # if shelf_set_source_path is not None we load the source path and return + + # if the shelf set name already exists, do nothing, else, create a new one + + # go through each shelf + # if shelf_file_path exists, load the shelf and return + # if the shelf name already exists, do nothing, else, create a new one + + # go through each tool + # if filepath exists, load the tool, add it to the shelf and continue + # create the tool + # add it to a list of tools + + # add the tools list to the shelf with the tools already in it + # add the shelf to the shelf set with the shelfs already in it + + +def get_or_create_shelf_set(): + pass + + +def get_or_create_shelf(): + pass + + +def get_or_create_tool(): + pass From b74655c167aefd2d84e8cd6716d7b7b3c02783cd Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 10 Aug 2022 11:56:05 +0200 Subject: [PATCH 010/144] set multipath to false for shelf set path --- .../settings/defaults/project_settings/houdini.json | 6 +++++- .../schemas/schema_houdini_scriptshelf.json | 10 +++++----- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 5805f600c5..2ceed37935 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -2,7 +2,11 @@ "shelves": [ { "shelf_set_name": "OpenPype Shelves", - "shelf_set_source_path": "/path/to/your/shelf_set_file", + "shelf_set_source_path": { + "windows": "", + "darwin": "", + "linux": "/path/to/your/shelf_set_file" + }, "shelf_definition": [ { "shelf_name": "OpenPype Shelf", diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json index 5a84c6d5cc..ae05cef74e 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json @@ -16,7 +16,7 @@ "type": "path", "key": "shelf_set_source_path", "label": "Shelf Set Path", - "multipath": true, + "multipath": false, "multiplatform": true }, { @@ -33,7 +33,7 @@ "label": "Shelf Name" }, { - "type": "text", + "type": "path", "key": "shelf_file_path", "label": "Shelf File Path" }, @@ -51,17 +51,17 @@ "label": "Name" }, { - "type": "text", + "type": "path", "key": "filepath", "label": "File Path" }, { - "type": "text", + "type": "path", "key": "script", "label": "Script" }, { - "type": "text", + "type": "path", "key": "icon", "label": "Icon" }, From a302caf6bd431b98136ce5b41c56cb0c60e49b4f Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 10 Aug 2022 13:05:37 +0200 Subject: [PATCH 011/144] setting shelf set filepath if any in right OS --- openpype/hosts/houdini/api/shelves.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index b8f6419175..6ea4b4a9fd 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -1,26 +1,38 @@ import os import logging +import platform from openpype.settings import get_project_settings -log = logging.getLogger(__name__) +import hou + +log = logging.getLogger("openpype.hosts.houdini") def generate_shelves(): + current_os = platform.system().lower() # load configuration of custom menu project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) shelves_set_config = project_settings["houdini"]["shelves"] if not shelves_set_config: - log.warning("No custom shelves found.") + log.warning( + "SHELF ERROR: No custom shelves found in project settings." + ) return # run the shelf generator for Houdini - for shelf_set in shelves_set_config: - pass - # if shelf_set_source_path is not None we load the source path and return + for shelf_set_config in shelves_set_config: + shelf_set_filepath = shelf_set_config.get('shelf_set_source_path') + # if shelf_set_source_path is not None we load the source path and continue + if shelf_set_filepath[current_os]: + hou.shelves.newShelfSet(file_path=shelf_set_filepath[current_os]) + # hou.ShelfSet.setFilePath(file_path=shelf_set_filepath[operating_system]) + continue # if the shelf set name already exists, do nothing, else, create a new one + shelf_set_name = shelf_set_config.get('shelf_set_name') + shelf_set = get_or_create_shelf_set(shelf_set_name) # go through each shelf # if shelf_file_path exists, load the shelf and return @@ -35,8 +47,9 @@ def generate_shelves(): # add the shelf to the shelf set with the shelfs already in it -def get_or_create_shelf_set(): - pass +def get_or_create_shelf_set(shelf_set_name): + log.warning("IN GET OR CREATE SHELF SET: {}".format(shelf_set_name)) + hou.shelves.shelves() def get_or_create_shelf(): From 6f68b998965893401d1679913084942e7329b086 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 10 Aug 2022 15:47:32 +0200 Subject: [PATCH 012/144] Fix refactor typo --- openpype/hosts/maya/api/lib_rendersetup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/lib_rendersetup.py b/openpype/hosts/maya/api/lib_rendersetup.py index a00be52d8e..e616f26e1b 100644 --- a/openpype/hosts/maya/api/lib_rendersetup.py +++ b/openpype/hosts/maya/api/lib_rendersetup.py @@ -367,9 +367,9 @@ def get_shader_in_layer(node, layer): """ - def _get_connected_shader(shape): + def _get_connected_shader(plug): """Return current shader""" - return cmds.listConnections(shape + ".instObjGroups", + return cmds.listConnections(plug, source=False, destination=True, plugs=False, From b69e2e2003f768b111fa50635a0c5f3268ca7357 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 10 Aug 2022 17:34:48 +0200 Subject: [PATCH 013/144] get shelf set or create one --- openpype/hosts/houdini/api/shelves.py | 29 ++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index 6ea4b4a9fd..d89f3153ea 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -21,13 +21,19 @@ def generate_shelves(): ) return - # run the shelf generator for Houdini for shelf_set_config in shelves_set_config: shelf_set_filepath = shelf_set_config.get('shelf_set_source_path') + # if shelf_set_source_path is not None we load the source path and continue if shelf_set_filepath[current_os]: + if not os.path.isfile(shelf_set_filepath[current_os]): + raise FileNotFoundError( + "SHELF ERROR: This path doesn't exist - {}".format( + shelf_set_filepath[current_os] + ) + ) + hou.shelves.newShelfSet(file_path=shelf_set_filepath[current_os]) - # hou.ShelfSet.setFilePath(file_path=shelf_set_filepath[operating_system]) continue # if the shelf set name already exists, do nothing, else, create a new one @@ -47,9 +53,22 @@ def generate_shelves(): # add the shelf to the shelf set with the shelfs already in it -def get_or_create_shelf_set(shelf_set_name): - log.warning("IN GET OR CREATE SHELF SET: {}".format(shelf_set_name)) - hou.shelves.shelves() +def get_or_create_shelf_set(shelf_set_label): + all_shelves = hou.shelves.shelfSets().values() + + shelf_set = [ + shelf for shelf in all_shelves if shelf.label() == shelf_set_label + ] + + if shelf_set: + return shelf_set[0] + + shelf_set_name = shelf_set_label.replace(' ', '_').lower() + new_shelf_set = hou.shelves.newShelfSet( + name=shelf_set_name, + label=shelf_set_label + ) + return new_shelf_set def get_or_create_shelf(): From 937ba13ea0c62b63d2d56a0f1895932089070983 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 10 Aug 2022 17:47:33 +0200 Subject: [PATCH 014/144] remove filepath for shelf and tools --- .../settings/defaults/project_settings/houdini.json | 2 -- .../schemas/schema_houdini_scriptshelf.json | 12 +----------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 2ceed37935..a818f82d6b 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -10,11 +10,9 @@ "shelf_definition": [ { "shelf_name": "OpenPype Shelf", - "shelf_file_path": "/path/to/your/shelf_file", "tools_list": [ { "name": "OpenPype Tool", - "filepath": "/path/to/your/tool_file", "script": "/path/to/your/tool_script", "icon": "/path/to/your/icon", "help": "Help message for your tool" diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json index ae05cef74e..812ab7d8c9 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json @@ -15,7 +15,7 @@ { "type": "path", "key": "shelf_set_source_path", - "label": "Shelf Set Path", + "label": "Shelf Set Path (optional)", "multipath": false, "multiplatform": true }, @@ -32,11 +32,6 @@ "key": "shelf_name", "label": "Shelf Name" }, - { - "type": "path", - "key": "shelf_file_path", - "label": "Shelf File Path" - }, { "type": "list", "key": "tools_list", @@ -50,11 +45,6 @@ "key": "name", "label": "Name" }, - { - "type": "path", - "key": "filepath", - "label": "File Path" - }, { "type": "path", "key": "script", From ea37f4c3c5313e6c088e533c10c721b33d490333 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 10 Aug 2022 18:06:38 +0200 Subject: [PATCH 015/144] get or create shelf implementation --- openpype/hosts/houdini/api/shelves.py | 52 +++++++++++++++++++++------ 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index d89f3153ea..76fe0cbd87 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -1,3 +1,4 @@ +from cProfile import label import os import logging import platform @@ -17,14 +18,13 @@ def generate_shelves(): if not shelves_set_config: log.warning( - "SHELF ERROR: No custom shelves found in project settings." + "SHELF WARNGING: No custom shelves found in project settings." ) return for shelf_set_config in shelves_set_config: shelf_set_filepath = shelf_set_config.get('shelf_set_source_path') - # if shelf_set_source_path is not None we load the source path and continue if shelf_set_filepath[current_os]: if not os.path.isfile(shelf_set_filepath[current_os]): raise FileNotFoundError( @@ -36,13 +36,33 @@ def generate_shelves(): hou.shelves.newShelfSet(file_path=shelf_set_filepath[current_os]) continue - # if the shelf set name already exists, do nothing, else, create a new one shelf_set_name = shelf_set_config.get('shelf_set_name') + if not shelf_set_name: + log.warning( + "SHELF WARNGING: No name found in shelf set definition." + ) + return + shelf_set = get_or_create_shelf_set(shelf_set_name) - # go through each shelf - # if shelf_file_path exists, load the shelf and return - # if the shelf name already exists, do nothing, else, create a new one + shelves_definition = shelf_set_config.get('shelf_definition') + + if not shelves_definition: + log.warning( + "SHELF WARNING: \ +No shelf definition found for shelf set named '{}'".format(shelf_set_name) + ) + return + + for shelf_definition in shelves_definition: + shelf_name = shelf_definition.get('shelf_name') + if not shelf_name: + log.warning( + "SHELF WARNGING: No name found in shelf set definition." + ) + return + + shelf = get_or_create_shelf(shelf_name) # go through each tool # if filepath exists, load the tool, add it to the shelf and continue @@ -54,10 +74,10 @@ def generate_shelves(): def get_or_create_shelf_set(shelf_set_label): - all_shelves = hou.shelves.shelfSets().values() + all_shelves_sets = hou.shelves.shelfSets().values() shelf_set = [ - shelf for shelf in all_shelves if shelf.label() == shelf_set_label + shelf for shelf in all_shelves_sets if shelf.label() == shelf_set_label ] if shelf_set: @@ -71,8 +91,20 @@ def get_or_create_shelf_set(shelf_set_label): return new_shelf_set -def get_or_create_shelf(): - pass +def get_or_create_shelf(shelf_label): + all_shelves = hou.shelves.shelves().values() + + shelf = [s for s in all_shelves if s.label() == shelf_label] + + if shelf: + return shelf[0] + + shelf_name = shelf_label.replace(' ', '_').lower() + new_shelf = hou.shelves.newShelf( + name=shelf_name, + label=shelf_label + ) + return new_shelf def get_or_create_tool(): From a6ddb2d44b9ec9edb76c2a41f1b471909afabde6 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 11 Aug 2022 11:44:42 +0200 Subject: [PATCH 016/144] filter mandatory attributes for tool --- openpype/hosts/houdini/api/shelves.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index 76fe0cbd87..a37ec88d64 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -64,8 +64,17 @@ No shelf definition found for shelf set named '{}'".format(shelf_set_name) shelf = get_or_create_shelf(shelf_name) - # go through each tool - # if filepath exists, load the tool, add it to the shelf and continue + tools = [] + for tool in shelf_definition.get('tools_list'): + mandatory_attributes = ['name', 'script'] + if not all( + [v for k, v in tool.items() if k in mandatory_attributes] + ): + log.warning("TOOLS ERROR: You need to specify at least \ +the name and the script path of the tool.") + return + + tool = get_or_create_tool(tool, shelf) # create the tool # add it to a list of tools @@ -107,5 +116,5 @@ def get_or_create_shelf(shelf_label): return new_shelf -def get_or_create_tool(): +def get_or_create_tool(tool_definition, shelf): pass From eab14fc5e9204126554e8be5898010e0db0398ca Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Aug 2022 13:20:27 +0200 Subject: [PATCH 017/144] Include inputVersions with the publish job instance metadata --- .../modules/deadline/plugins/publish/submit_publish_job.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_publish_job.py b/openpype/modules/deadline/plugins/publish/submit_publish_job.py index f05ef31938..2fa7da5dac 100644 --- a/openpype/modules/deadline/plugins/publish/submit_publish_job.py +++ b/openpype/modules/deadline/plugins/publish/submit_publish_job.py @@ -774,7 +774,9 @@ class ProcessSubmittedJobOnFarm(pyblish.api.InstancePlugin): "resolutionHeight": data.get("resolutionHeight", 1080), "multipartExr": data.get("multipartExr", False), "jobBatchName": data.get("jobBatchName", ""), - "useSequenceForReview": data.get("useSequenceForReview", True) + "useSequenceForReview": data.get("useSequenceForReview", True), + # map inputVersions `ObjectId` -> `str` so json supports it + "inputVersions": list(map(str, data.get("inputVersions", []))) } # skip locking version if we are creating v01 From 161ae6ef77f0ac0f2017e7b64fdd50331c03592d Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 11 Aug 2022 14:59:52 +0200 Subject: [PATCH 018/144] change key 'name' by 'label' for tool name --- openpype/settings/defaults/project_settings/houdini.json | 2 +- .../projects_schema/schemas/schema_houdini_scriptshelf.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index a818f82d6b..78e0d595cf 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -12,7 +12,7 @@ "shelf_name": "OpenPype Shelf", "tools_list": [ { - "name": "OpenPype Tool", + "label": "OpenPype Tool", "script": "/path/to/your/tool_script", "icon": "/path/to/your/icon", "help": "Help message for your tool" diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json index 812ab7d8c9..bab9b604b4 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_houdini_scriptshelf.json @@ -42,7 +42,7 @@ "children": [ { "type": "text", - "key": "name", + "key": "label", "label": "Name" }, { From 778140b388c57ef8af0c4f69250cebf673dd6e74 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 11 Aug 2022 16:21:23 +0200 Subject: [PATCH 019/144] add tool creation and adding tool to shelf and shelf to shelf_set --- openpype/hosts/houdini/api/shelves.py | 49 +++++++++++++++++++++------ 1 file changed, 39 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index a37ec88d64..0687e2f519 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -1,4 +1,3 @@ -from cProfile import label import os import logging import platform @@ -64,22 +63,23 @@ No shelf definition found for shelf set named '{}'".format(shelf_set_name) shelf = get_or_create_shelf(shelf_name) - tools = [] - for tool in shelf_definition.get('tools_list'): + for tool_definition in shelf_definition.get('tools_list'): mandatory_attributes = ['name', 'script'] if not all( - [v for k, v in tool.items() if k in mandatory_attributes] + [v for k, v in tool_definition.items() if + k in mandatory_attributes] ): log.warning("TOOLS ERROR: You need to specify at least \ the name and the script path of the tool.") return - tool = get_or_create_tool(tool, shelf) - # create the tool - # add it to a list of tools + tool = get_or_create_tool(tool_definition, shelf) - # add the tools list to the shelf with the tools already in it - # add the shelf to the shelf set with the shelfs already in it + if tool not in shelf.tools(): + shelf.setTools(list(shelf.tools()) + [tool]) + + if shelf not in shelf_set.shelves(): + shelf_set.setShelves(shelf_set.shelves() + (shelf,)) def get_or_create_shelf_set(shelf_set_label): @@ -117,4 +117,33 @@ def get_or_create_shelf(shelf_label): def get_or_create_tool(tool_definition, shelf): - pass + existing_tools = shelf.tools() + tool_label = tool_definition.get('label') + + existing_tool = [ + tool for tool in existing_tools if tool.label() == tool_label + ] + + if existing_tool: + tool_definition.pop('name', None) + tool_definition.pop('label', None) + existing_tool[0].setData(**tool_definition) + return existing_tool[0] + + tool_name = tool_label.replace(' ', '_').lower() + + if not os.path.exists(tool_definition['script']): + log.warning( + "TOOL ERROR: This path doesn't exist - {}".format( + tool_definition['script'] + ) + ) + return + + with open(tool_definition['script']) as f: + script = f.read() + tool_definition.update({'script': script}) + + new_tool = hou.shelves.newTool(name=tool_name, **tool_definition) + + return new_tool From 532432d81739b2996ae94e56b6bf2faf36498dc3 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 11 Aug 2022 17:57:38 +0200 Subject: [PATCH 020/144] add docstrings --- openpype/hosts/houdini/api/shelves.py | 43 +++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index 0687e2f519..bb92aa828e 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -10,8 +10,15 @@ log = logging.getLogger("openpype.hosts.houdini") def generate_shelves(): + """This function generates complete shelves from shef set to tools + in Houdini from openpype project settings houdini shelf definition. + + Raises: + FileNotFoundError: Raised when the shelf set filepath does not exist + """ current_os = platform.system().lower() - # load configuration of custom menu + + # load configuration of houdini shelves project_settings = get_project_settings(os.getenv("AVALON_PROJECT")) shelves_set_config = project_settings["houdini"]["shelves"] @@ -57,13 +64,15 @@ No shelf definition found for shelf set named '{}'".format(shelf_set_name) shelf_name = shelf_definition.get('shelf_name') if not shelf_name: log.warning( - "SHELF WARNGING: No name found in shelf set definition." + "SHELF WARNGING: No name found in shelf definition." ) return shelf = get_or_create_shelf(shelf_name) for tool_definition in shelf_definition.get('tools_list'): + # We verify that the name and script attibutes of the tool + # are set mandatory_attributes = ['name', 'script'] if not all( [v for k, v in tool_definition.items() if @@ -75,14 +84,25 @@ the name and the script path of the tool.") tool = get_or_create_tool(tool_definition, shelf) + # Add the tool to the shelf if not already in it if tool not in shelf.tools(): shelf.setTools(list(shelf.tools()) + [tool]) + # Add the shelf in the shelf set if not already in it if shelf not in shelf_set.shelves(): shelf_set.setShelves(shelf_set.shelves() + (shelf,)) def get_or_create_shelf_set(shelf_set_label): + """This function verifies if the shelf set label exists. If not, + creates a new shelf set. + + Arguments: + shelf_set_label {str} -- The label of the shelf set + + Returns: + hou.ShelfSet -- The shelf set existing or the new one + """ all_shelves_sets = hou.shelves.shelfSets().values() shelf_set = [ @@ -101,6 +121,15 @@ def get_or_create_shelf_set(shelf_set_label): def get_or_create_shelf(shelf_label): + """This function verifies if the shelf label exists. If not, creates + a new shelf. + + Arguments: + shelf_label {str} -- The label of the shelf + + Returns: + hou.Shelf -- The shelf existing or the new one + """ all_shelves = hou.shelves.shelves().values() shelf = [s for s in all_shelves if s.label() == shelf_label] @@ -117,6 +146,16 @@ def get_or_create_shelf(shelf_label): def get_or_create_tool(tool_definition, shelf): + """This function verifies if the tool exsist and update it. If not, creates + a new one. + + Arguments: + tool_definition {dict} -- Dict with label, script, icon and help + shelf {hou.Shelf} -- The parent shelf of the tool + + Returns: + hou.Tool -- The tool updated or the new one + """ existing_tools = shelf.tools() tool_label = tool_definition.get('label') From 74161e931e17a736c3dae6ae24678f9db4d497d0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Aug 2022 18:02:47 +0200 Subject: [PATCH 021/144] Show whether a subset is loaded into the current scene --- openpype/tools/loader/model.py | 157 +++++++++++++++++++------------ openpype/tools/loader/widgets.py | 3 +- 2 files changed, 101 insertions(+), 59 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index a5174bd804..3130f879df 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -14,9 +14,11 @@ from openpype.client import ( get_versions, get_hero_versions, get_version_by_name, - get_representations + get_representations, + get_representations_parents ) from openpype.pipeline import ( + registered_host, HeroVersionType, schema, ) @@ -136,7 +138,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "duration", "handles", "step", - "repre_info" + "repre_info", + "loaded_in_scene" ] column_labels_mapping = { @@ -150,7 +153,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "duration": "Duration", "handles": "Handles", "step": "Step", - "repre_info": "Availability" + "repre_info": "Availability", + "loaded_in_scene": "In scene" } SortAscendingRole = QtCore.Qt.UserRole + 2 @@ -231,8 +235,14 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self._doc_fetching_stop = False self._doc_payload = {} - self.doc_fetched.connect(self._on_doc_fetched) + self._host = registered_host() + self._loaded_representation_ids = set() + # Refresh loaded scene containers only every 3 seconds at most + self._host_loaded_refresh_timeout = 3 + self._host_loaded_refresh_time = 0 + + self.doc_fetched.connect(self._on_doc_fetched) self.refresh() def get_item_by_id(self, item_id): @@ -472,6 +482,29 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): last_versions_by_subset_id[subset_id] = hero_version + # Check loaded subsets + subsets_loaded_by_id = set() + ids = self._loaded_representation_ids + if ids: + if self._doc_fetching_stop: + return + + # Get subsets from representations + # todo: optimize with aggregation query to distinct subset id + representations = get_representations(project_name, + representation_ids=ids, + fields=["parent"]) + parents_by_repre_id = get_representations_parents( + project_name, + representations=representations + ) + for repre_id, repre_parents in parents_by_repre_id.items(): + _, repre_subset, _, _ = repre_parents + subsets_loaded_by_id.add(repre_subset["_id"]) + + if self._doc_fetching_stop: + return + repre_info = {} if self.sync_server.enabled: version_ids = set() @@ -494,7 +527,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "subset_docs_by_id": subset_docs_by_id, "subset_families": subset_families, "last_versions_by_subset_id": last_versions_by_subset_id, - "repre_info_by_version_id": repre_info + "repre_info_by_version_id": repre_info, + "subsets_loaded_by_id": subsets_loaded_by_id } self.doc_fetched.emit() @@ -526,6 +560,17 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): self.doc_fetched.emit() return + # Collect scene container representations to compare loaded state + # This runs in the main thread because it involves the host DCC + if self._host: + time_since_refresh = time.time() - self._host_loaded_refresh_time + print(time_since_refresh) + if time_since_refresh > self._host_loaded_refresh_timeout: + repre_ids = {con.get("representation") + for con in self._host.ls()} + self._loaded_representation_ids = repre_ids + self._host_loaded_refresh_time = time.time() + self.fetch_subset_and_version() def _on_doc_fetched(self): @@ -547,6 +592,10 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "repre_info_by_version_id" ) + subsets_loaded_by_id = self._doc_payload.get( + "subsets_loaded_by_id" + ) + if ( asset_docs_by_id is None or subset_docs_by_id is None @@ -561,7 +610,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): asset_docs_by_id, subset_docs_by_id, last_versions_by_subset_id, - repre_info_by_version_id + repre_info_by_version_id, + subsets_loaded_by_id ) self.endResetModel() self.refreshed.emit(True) @@ -589,8 +639,12 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): return merge_group def _fill_subset_items( - self, asset_docs_by_id, subset_docs_by_id, last_versions_by_subset_id, - repre_info_by_version_id + self, + asset_docs_by_id, + subset_docs_by_id, + last_versions_by_subset_id, + repre_info_by_version_id, + subsets_loaded_by_id ): _groups_tuple = self.groups_config.split_subsets_for_groups( subset_docs_by_id.values(), self._grouping @@ -614,6 +668,37 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "index": self.index(group_item.row(), 0) } + def _add_subset_item(subset_doc, parent_item, parent_index): + last_version = last_versions_by_subset_id.get( + subset_doc["_id"] + ) + # do not show subset without version + if not last_version: + return + + data = copy.deepcopy(subset_doc) + data["subset"] = subset_doc["name"] + + asset_id = subset_doc["parent"] + data["asset"] = asset_docs_by_id[asset_id]["name"] + + data["last_version"] = last_version + + loaded = subset_doc["_id"] in subsets_loaded_by_id + data["loaded_in_scene"] = "yes" if loaded else "no" + + # Sync server data + data.update( + self._get_last_repre_info(repre_info_by_version_id, + last_version["_id"])) + + item = Item() + item.update(data) + self.add_child(item, parent_item) + + index = self.index(item.row(), 0, parent_index) + self.set_version(index, last_version) + subset_counter = 0 for group_name, subset_docs_by_name in subset_docs_by_group.items(): parent_item = group_item_by_name[group_name]["item"] @@ -636,31 +721,9 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): _parent_index = parent_index for subset_doc in subset_docs: - asset_id = subset_doc["parent"] - - data = copy.deepcopy(subset_doc) - data["subset"] = subset_name - data["asset"] = asset_docs_by_id[asset_id]["name"] - - last_version = last_versions_by_subset_id.get( - subset_doc["_id"] - ) - data["last_version"] = last_version - - # do not show subset without version - if not last_version: - continue - - data.update( - self._get_last_repre_info(repre_info_by_version_id, - last_version["_id"])) - - item = Item() - item.update(data) - self.add_child(item, _parent_item) - - index = self.index(item.row(), 0, _parent_index) - self.set_version(index, last_version) + _add_subset_item(subset_doc, + parent_item=_parent_item, + parent_index=_parent_index) for subset_name in sorted(subset_docs_without_group.keys()): subset_docs = subset_docs_without_group[subset_name] @@ -675,31 +738,9 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): subset_counter += 1 for subset_doc in subset_docs: - asset_id = subset_doc["parent"] - - data = copy.deepcopy(subset_doc) - data["subset"] = subset_name - data["asset"] = asset_docs_by_id[asset_id]["name"] - - last_version = last_versions_by_subset_id.get( - subset_doc["_id"] - ) - data["last_version"] = last_version - - # do not show subset without version - if not last_version: - continue - - data.update( - self._get_last_repre_info(repre_info_by_version_id, - last_version["_id"])) - - item = Item() - item.update(data) - self.add_child(item, parent_item) - - index = self.index(item.row(), 0, parent_index) - self.set_version(index, last_version) + _add_subset_item(subset_doc, + parent_item=parent_item, + parent_index=parent_index) def data(self, index, role): if not index.isValid(): diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 48c038418a..3c4a89aa0f 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -168,7 +168,8 @@ class SubsetWidget(QtWidgets.QWidget): ("duration", 60), ("handles", 55), ("step", 10), - ("repre_info", 65) + ("repre_info", 65), + ("loaded_in_scene", 20) ) def __init__( From a07650226a3fe54eb3becfbc16881acf7e8ae6cf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 11 Aug 2022 18:09:19 +0200 Subject: [PATCH 022/144] Remove unused variables --- openpype/tools/loader/model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 3130f879df..6cb9ba2c6d 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -498,8 +498,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): project_name, representations=representations ) - for repre_id, repre_parents in parents_by_repre_id.items(): - _, repre_subset, _, _ = repre_parents + for repre_parents in parents_by_repre_id.values(): + repre_subset = repre_parents[1] subsets_loaded_by_id.add(repre_subset["_id"]) if self._doc_fetching_stop: From 56dad829047afc22ea61d5281660f18b47e9b585 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Thu, 11 Aug 2022 18:13:18 +0200 Subject: [PATCH 023/144] admin docs for houdini shelves manager --- website/docs/admin_hosts_houdini.md | 11 +++++++++++ .../assets/houdini-admin_shelvesmanager.png | Bin 0 -> 28464 bytes website/sidebars.js | 5 +++-- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 website/docs/admin_hosts_houdini.md create mode 100644 website/docs/assets/houdini-admin_shelvesmanager.png diff --git a/website/docs/admin_hosts_houdini.md b/website/docs/admin_hosts_houdini.md new file mode 100644 index 0000000000..64c54db591 --- /dev/null +++ b/website/docs/admin_hosts_houdini.md @@ -0,0 +1,11 @@ +--- +id: admin_hosts_houdini +title: Houdini +sidebar_label: Houdini +--- + +## Shelves Manager +You can add your custom shelf set into Houdini by setting your shelf sets, shelves and tools in **Houdini -> Shelves Manager**. +![Custom menu definition](assets/houdini-admin_shelvesmanager.png) + +The Shelf Set Path is used to load a .shelf file to generate your shelf set. If the path is specified, you don't have to set the shelves and tools. \ No newline at end of file diff --git a/website/docs/assets/houdini-admin_shelvesmanager.png b/website/docs/assets/houdini-admin_shelvesmanager.png new file mode 100644 index 0000000000000000000000000000000000000000..ba2f15a6a5eb5af0286a4f1485ddd97b351beab6 GIT binary patch literal 28464 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 literal 0 HcmV?d00001 diff --git a/website/sidebars.js b/website/sidebars.js index 9d60a5811c..6ccfc42180 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -101,6 +101,7 @@ module.exports = { items: [ "admin_hosts_blender", "admin_hosts_hiero", + "admin_hosts_houdini", "admin_hosts_maya", "admin_hosts_nuke", "admin_hosts_resolve", @@ -146,7 +147,7 @@ module.exports = { ], }, ], - Dev: [ + Dev: [ "dev_introduction", "dev_requirements", "dev_build", @@ -160,5 +161,5 @@ module.exports = { "dev_publishing" ] } - ] + ] }; From b3517a2da945b72b6b13eff292fa7d6ed63861b0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Aug 2022 09:47:21 +0200 Subject: [PATCH 024/144] Remove print statement --- openpype/tools/loader/model.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 6cb9ba2c6d..d9b1c708e0 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -564,7 +564,6 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): # This runs in the main thread because it involves the host DCC if self._host: time_since_refresh = time.time() - self._host_loaded_refresh_time - print(time_since_refresh) if time_since_refresh > self._host_loaded_refresh_timeout: repre_ids = {con.get("representation") for con in self._host.ls()} From ca40a71f5c33a1f557039661bbbdd8db5d22738b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 12 Aug 2022 09:48:52 +0200 Subject: [PATCH 025/144] Reduce queries to get loaded subset ids --- openpype/tools/loader/model.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index d9b1c708e0..9d1f1e045c 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -483,24 +483,22 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): last_versions_by_subset_id[subset_id] = hero_version # Check loaded subsets - subsets_loaded_by_id = set() + loaded_subset_ids = set() ids = self._loaded_representation_ids if ids: if self._doc_fetching_stop: return - # Get subsets from representations + # Get subset ids from loaded representations in workfile # todo: optimize with aggregation query to distinct subset id representations = get_representations(project_name, representation_ids=ids, fields=["parent"]) - parents_by_repre_id = get_representations_parents( - project_name, - representations=representations - ) - for repre_parents in parents_by_repre_id.values(): - repre_subset = repre_parents[1] - subsets_loaded_by_id.add(repre_subset["_id"]) + version_ids = set(repre["parent"] for repre in representations) + versions = get_versions(project_name, + version_ids=version_ids, + fields=["parent"]) + loaded_subset_ids = set(version["parent"] for version in versions) if self._doc_fetching_stop: return @@ -528,7 +526,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "subset_families": subset_families, "last_versions_by_subset_id": last_versions_by_subset_id, "repre_info_by_version_id": repre_info, - "subsets_loaded_by_id": subsets_loaded_by_id + "subsets_loaded_by_id": loaded_subset_ids } self.doc_fetched.emit() From e48eb3ba47f9afc41ce3c4c06b6e7ffb36746f89 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 16 Aug 2022 11:15:01 +0200 Subject: [PATCH 026/144] remove create_shelf function since it is no longer needed --- openpype/hosts/houdini/api/lib.py | 32 ------------------------------- 1 file changed, 32 deletions(-) diff --git a/openpype/hosts/houdini/api/lib.py b/openpype/hosts/houdini/api/lib.py index 55832abeb3..c8a7f92bb9 100644 --- a/openpype/hosts/houdini/api/lib.py +++ b/openpype/hosts/houdini/api/lib.py @@ -460,35 +460,3 @@ def reset_framerange(): hou.playbar.setFrameRange(frame_start, frame_end) hou.playbar.setPlaybackRange(frame_start, frame_end) hou.setFrame(frame_start) - - -def create_shelf(): - hou.shelves.beginChangeBlock() - - custom_shelf = hou.shelves.newShelf( - file_path='', - name="custom_shelf", - label="Custom Shelf" - ) - - new_tool = hou.shelves.newTool( - file_path='', - name='new_tool', - label='New Tool', - script='', - language=hou.scriptLanguage.Python, - icon='', - help='This is a new tool' - ) - - if new_tool not in custom_shelf.tools(): - custom_shelf.setTools(list(custom_shelf.tools()) + [new_tool]) - - shelf_set = [ - shelf for shelf in hou.shelves.shelfSets().values() - if shelf.label() == "Create and Refine" - ][0] - - shelf_set.setShelves(shelf_set.shelves() + (custom_shelf,)) - - hou.shelves.endChangeBlock() From c9f60bb848b81f9b4c095281cfae3c3d27e8d652 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 16 Aug 2022 11:19:50 +0200 Subject: [PATCH 027/144] remove invalid default values --- .../defaults/project_settings/houdini.json | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index 78e0d595cf..43d2ad132a 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -5,21 +5,8 @@ "shelf_set_source_path": { "windows": "", "darwin": "", - "linux": "/path/to/your/shelf_set_file" - }, - "shelf_definition": [ - { - "shelf_name": "OpenPype Shelf", - "tools_list": [ - { - "label": "OpenPype Tool", - "script": "/path/to/your/tool_script", - "icon": "/path/to/your/icon", - "help": "Help message for your tool" - } - ] - } - ] + "linux": "" + } } ], "create": { From 9c7bcb84aa42a2f2c083c856ae421a9d264f32dc Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 16 Aug 2022 12:25:05 +0200 Subject: [PATCH 028/144] fix typo and tool creation --- openpype/hosts/houdini/api/shelves.py | 33 +++++++++++++++++---------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index bb92aa828e..d9a3a34da6 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -10,7 +10,7 @@ log = logging.getLogger("openpype.hosts.houdini") def generate_shelves(): - """This function generates complete shelves from shef set to tools + """This function generates complete shelves from shelf set to tools in Houdini from openpype project settings houdini shelf definition. Raises: @@ -23,8 +23,8 @@ def generate_shelves(): shelves_set_config = project_settings["houdini"]["shelves"] if not shelves_set_config: - log.warning( - "SHELF WARNGING: No custom shelves found in project settings." + log.info( + "SHELF INFO: No custom shelves found in project settings." ) return @@ -45,7 +45,7 @@ def generate_shelves(): shelf_set_name = shelf_set_config.get('shelf_set_name') if not shelf_set_name: log.warning( - "SHELF WARNGING: No name found in shelf set definition." + "SHELF WARNING: No name found in shelf set definition." ) return @@ -54,8 +54,8 @@ def generate_shelves(): shelves_definition = shelf_set_config.get('shelf_definition') if not shelves_definition: - log.warning( - "SHELF WARNING: \ + log.info( + "SHELF INFO: \ No shelf definition found for shelf set named '{}'".format(shelf_set_name) ) return @@ -64,26 +64,34 @@ No shelf definition found for shelf set named '{}'".format(shelf_set_name) shelf_name = shelf_definition.get('shelf_name') if not shelf_name: log.warning( - "SHELF WARNGING: No name found in shelf definition." + "SHELF WARNING: No name found in shelf definition." ) return shelf = get_or_create_shelf(shelf_name) + if not shelf_definition.get('tools_list'): + log.warning("TOOLS INFO: No tool definition found for \ +shelf named {}".format(shelf_name)) + return + + mandatory_attributes = ['name', 'script'] for tool_definition in shelf_definition.get('tools_list'): # We verify that the name and script attibutes of the tool # are set - mandatory_attributes = ['name', 'script'] if not all( [v for k, v in tool_definition.items() if k in mandatory_attributes] ): log.warning("TOOLS ERROR: You need to specify at least \ the name and the script path of the tool.") - return + continue tool = get_or_create_tool(tool_definition, shelf) + if not tool: + return + # Add the tool to the shelf if not already in it if tool not in shelf.tools(): shelf.setTools(list(shelf.tools()) + [tool]) @@ -105,12 +113,12 @@ def get_or_create_shelf_set(shelf_set_label): """ all_shelves_sets = hou.shelves.shelfSets().values() - shelf_set = [ + shelf_sets = [ shelf for shelf in all_shelves_sets if shelf.label() == shelf_set_label ] - if shelf_set: - return shelf_set[0] + if shelf_sets: + return shelf_sets[0] shelf_set_name = shelf_set_label.replace(' ', '_').lower() new_shelf_set = hou.shelves.newShelfSet( @@ -170,6 +178,7 @@ def get_or_create_tool(tool_definition, shelf): return existing_tool[0] tool_name = tool_label.replace(' ', '_').lower() + log.warning(tool_definition) if not os.path.exists(tool_definition['script']): log.warning( From f5d7634e007d4e9a27f76b7abb693daa7b9ba055 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 16 Aug 2022 14:15:08 +0200 Subject: [PATCH 029/144] change tools mandatory attributes to set type and iterate only on those attributes --- openpype/hosts/houdini/api/shelves.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index d9a3a34da6..498fffc7cd 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -75,13 +75,12 @@ No shelf definition found for shelf set named '{}'".format(shelf_set_name) shelf named {}".format(shelf_name)) return - mandatory_attributes = ['name', 'script'] + mandatory_attributes = {'name', 'script'} for tool_definition in shelf_definition.get('tools_list'): # We verify that the name and script attibutes of the tool # are set if not all( - [v for k, v in tool_definition.items() if - k in mandatory_attributes] + tool_definition[key] for key in mandatory_attributes ): log.warning("TOOLS ERROR: You need to specify at least \ the name and the script path of the tool.") From e0abb7245c231d2cabec782b1172b9257fd096da Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 16 Aug 2022 14:34:15 +0200 Subject: [PATCH 030/144] fix type and docstring style to match OpenPype's --- openpype/hosts/houdini/api/pipeline.py | 1 - openpype/hosts/houdini/api/shelves.py | 5 ++--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index f809f0ce56..d7a8135d86 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -310,7 +310,6 @@ def _set_context_settings(): fps resolution renderer - shelves Returns: None diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index 498fffc7cd..725d162980 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -105,7 +105,7 @@ def get_or_create_shelf_set(shelf_set_label): creates a new shelf set. Arguments: - shelf_set_label {str} -- The label of the shelf set + shelf_set_label (str) -- The label of the shelf set Returns: hou.ShelfSet -- The shelf set existing or the new one @@ -153,7 +153,7 @@ def get_or_create_shelf(shelf_label): def get_or_create_tool(tool_definition, shelf): - """This function verifies if the tool exsist and update it. If not, creates + """This function verifies if the tool exsists and updates it. If not, creates a new one. Arguments: @@ -177,7 +177,6 @@ def get_or_create_tool(tool_definition, shelf): return existing_tool[0] tool_name = tool_label.replace(' ', '_').lower() - log.warning(tool_definition) if not os.path.exists(tool_definition['script']): log.warning( From 129a38ebc0204fc9d6777a1b876d1f882fc929f4 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Tue, 16 Aug 2022 14:34:15 +0200 Subject: [PATCH 031/144] fix type and docstring style to match OpenPype's --- openpype/hosts/houdini/api/pipeline.py | 1 - openpype/hosts/houdini/api/shelves.py | 17 ++++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/houdini/api/pipeline.py b/openpype/hosts/houdini/api/pipeline.py index f809f0ce56..d7a8135d86 100644 --- a/openpype/hosts/houdini/api/pipeline.py +++ b/openpype/hosts/houdini/api/pipeline.py @@ -310,7 +310,6 @@ def _set_context_settings(): fps resolution renderer - shelves Returns: None diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index 498fffc7cd..ba3fcc2af9 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -105,10 +105,10 @@ def get_or_create_shelf_set(shelf_set_label): creates a new shelf set. Arguments: - shelf_set_label {str} -- The label of the shelf set + shelf_set_label (str): The label of the shelf set Returns: - hou.ShelfSet -- The shelf set existing or the new one + hou.ShelfSet: The shelf set existing or the new one """ all_shelves_sets = hou.shelves.shelfSets().values() @@ -132,10 +132,10 @@ def get_or_create_shelf(shelf_label): a new shelf. Arguments: - shelf_label {str} -- The label of the shelf + shelf_label (str): The label of the shelf Returns: - hou.Shelf -- The shelf existing or the new one + hou.Shelf: The shelf existing or the new one """ all_shelves = hou.shelves.shelves().values() @@ -153,15 +153,15 @@ def get_or_create_shelf(shelf_label): def get_or_create_tool(tool_definition, shelf): - """This function verifies if the tool exsist and update it. If not, creates + """This function verifies if the tool exsists and updates it. If not, creates a new one. Arguments: - tool_definition {dict} -- Dict with label, script, icon and help - shelf {hou.Shelf} -- The parent shelf of the tool + tool_definition (dict): Dict with label, script, icon and help + shelf (hou.Shelf): The parent shelf of the tool Returns: - hou.Tool -- The tool updated or the new one + hou.Tool: The tool updated or the new one """ existing_tools = shelf.tools() tool_label = tool_definition.get('label') @@ -177,7 +177,6 @@ def get_or_create_tool(tool_definition, shelf): return existing_tool[0] tool_name = tool_label.replace(' ', '_').lower() - log.warning(tool_definition) if not os.path.exists(tool_definition['script']): log.warning( From 4d61eec9952b61331c32b89d66fae35de26d13b4 Mon Sep 17 00:00:00 2001 From: Thomas Fricard <51854004+friquette@users.noreply.github.com> Date: Wed, 17 Aug 2022 10:12:43 +0200 Subject: [PATCH 032/144] fix typo Co-authored-by: Roy Nieterau --- openpype/hosts/houdini/api/shelves.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index ba3fcc2af9..a802d70457 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -153,7 +153,7 @@ def get_or_create_shelf(shelf_label): def get_or_create_tool(tool_definition, shelf): - """This function verifies if the tool exsists and updates it. If not, creates + """This function verifies if the tool exists and updates it. If not, creates a new one. Arguments: From ee4ad799902f313a98ac1e4ab1403617e2d7d4bf Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Wed, 17 Aug 2022 11:48:29 +0200 Subject: [PATCH 033/144] change logs messages --- openpype/hosts/houdini/api/shelves.py | 35 +++++++++++++++------------ 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index ba3fcc2af9..805ce4c397 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -6,7 +6,7 @@ from openpype.settings import get_project_settings import hou -log = logging.getLogger("openpype.hosts.houdini") +log = logging.getLogger("openpype.hosts.houdini.shelves") def generate_shelves(): @@ -23,8 +23,8 @@ def generate_shelves(): shelves_set_config = project_settings["houdini"]["shelves"] if not shelves_set_config: - log.info( - "SHELF INFO: No custom shelves found in project settings." + log.debug( + "No custom shelves found in project settings." ) return @@ -34,7 +34,7 @@ def generate_shelves(): if shelf_set_filepath[current_os]: if not os.path.isfile(shelf_set_filepath[current_os]): raise FileNotFoundError( - "SHELF ERROR: This path doesn't exist - {}".format( + "This path doesn't exist - {}".format( shelf_set_filepath[current_os] ) ) @@ -45,7 +45,7 @@ def generate_shelves(): shelf_set_name = shelf_set_config.get('shelf_set_name') if not shelf_set_name: log.warning( - "SHELF WARNING: No name found in shelf set definition." + "No name found in shelf set definition." ) return @@ -54,9 +54,10 @@ def generate_shelves(): shelves_definition = shelf_set_config.get('shelf_definition') if not shelves_definition: - log.info( - "SHELF INFO: \ -No shelf definition found for shelf set named '{}'".format(shelf_set_name) + log.debug( + "No shelf definition found for shelf set named '{}'".format( + shelf_set_name + ) ) return @@ -64,15 +65,18 @@ No shelf definition found for shelf set named '{}'".format(shelf_set_name) shelf_name = shelf_definition.get('shelf_name') if not shelf_name: log.warning( - "SHELF WARNING: No name found in shelf definition." + "No name found in shelf definition." ) return shelf = get_or_create_shelf(shelf_name) if not shelf_definition.get('tools_list'): - log.warning("TOOLS INFO: No tool definition found for \ -shelf named {}".format(shelf_name)) + log.debug( + "No tool definition found for shelf named {}".format( + shelf_name + ) + ) return mandatory_attributes = {'name', 'script'} @@ -82,8 +86,9 @@ shelf named {}".format(shelf_name)) if not all( tool_definition[key] for key in mandatory_attributes ): - log.warning("TOOLS ERROR: You need to specify at least \ -the name and the script path of the tool.") + log.warning( + "You need to specify at least the name and \ +the script path of the tool.") continue tool = get_or_create_tool(tool_definition, shelf) @@ -153,7 +158,7 @@ def get_or_create_shelf(shelf_label): def get_or_create_tool(tool_definition, shelf): - """This function verifies if the tool exsists and updates it. If not, creates + """This function verifies if the tool exists and updates it. If not, creates a new one. Arguments: @@ -180,7 +185,7 @@ def get_or_create_tool(tool_definition, shelf): if not os.path.exists(tool_definition['script']): log.warning( - "TOOL ERROR: This path doesn't exist - {}".format( + "This path doesn't exist - {}".format( tool_definition['script'] ) ) From a7190a51ad74823c069c73dbe8f7ec0f4c6daba6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 14:19:52 +0200 Subject: [PATCH 034/144] Force integer pixel values --- .../plugins/publish/submit_maya_deadline.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 7966861358..3ac9df07d6 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -141,17 +141,21 @@ def _format_tiles( out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = \ "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) - out["PluginInfo"]["RegionTop{}".format(tile)] = int(height) - (tile_y * h_space) # noqa: E501 - out["PluginInfo"]["RegionBottom{}".format(tile)] = int(height) - ((tile_y - 1) * h_space) - 1 # noqa: E501 - out["PluginInfo"]["RegionLeft{}".format(tile)] = (tile_x - 1) * w_space # noqa: E501 - out["PluginInfo"]["RegionRight{}".format(tile)] = (tile_x * w_space) - 1 # noqa: E501 + top = int(height) - (tile_y * h_space) + bottom = int(height) - ((tile_y - 1) * h_space) - 1 + left = (tile_x - 1) * w_space + right = (tile_x * w_space) - 1 + + out["PluginInfo"]["RegionTop{}".format(tile)] = int(top) + out["PluginInfo"]["RegionBottom{}".format(tile)] = int(bottom) + out["PluginInfo"]["RegionLeft{}".format(tile)] = int(left) + out["PluginInfo"]["RegionRight{}".format(tile)] = int(right) cfg["Tile{}".format(tile)] = new_filename cfg["Tile{}Tile".format(tile)] = new_filename cfg["Tile{}FileName".format(tile)] = new_filename - cfg["Tile{}X".format(tile)] = (tile_x - 1) * w_space - - cfg["Tile{}Y".format(tile)] = int(height) - (tile_y * h_space) + cfg["Tile{}X".format(tile)] = int(left) + cfg["Tile{}Y".format(tile)] = int(top) cfg["Tile{}Width".format(tile)] = w_space cfg["Tile{}Height".format(tile)] = h_space From f9c214e435a53ecbf8b5f0aba57292511a1e0873 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 17:02:23 +0200 Subject: [PATCH 035/144] Only apply `RenderSetupIncludeLights` when value is not None --- .../deadline/plugins/publish/submit_maya_deadline.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 3ac9df07d6..92c50c3e80 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -509,7 +509,15 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): self.payload_skeleton["JobInfo"]["Comment"] = comment self.payload_skeleton["PluginInfo"]["RenderLayer"] = renderlayer - self.payload_skeleton["PluginInfo"]["RenderSetupIncludeLights"] = instance.data.get("renderSetupIncludeLights") # noqa + # Only set RenderSetupIncludeLights when not None + rs_include_lights = instance.data.get("renderSetupIncludeLights") + if rs_include_lights is not None: + self.payload_skeleton["PluginInfo"]["RenderSetupIncludeLights"] = ( + rs_include_lights + ) + else: + self.payload_skeleton["PluginInfo"].pop("RenderSetupIncludeLights") + # Adding file dependencies. dependencies = instance.context.data["fileDependencies"] dependencies.append(filepath) From 37b2d85aa762ed4908fe29e3a57a11aef8e459cf Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 17:02:55 +0200 Subject: [PATCH 036/144] Fix indentation (shush hound) --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 92c50c3e80..0a18506bd4 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -513,7 +513,7 @@ class MayaSubmitDeadline(pyblish.api.InstancePlugin): rs_include_lights = instance.data.get("renderSetupIncludeLights") if rs_include_lights is not None: self.payload_skeleton["PluginInfo"]["RenderSetupIncludeLights"] = ( - rs_include_lights + rs_include_lights ) else: self.payload_skeleton["PluginInfo"].pop("RenderSetupIncludeLights") From 1f8c7e8ea527adfd16bf209781e2997d03f3e189 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 30 Aug 2022 20:48:23 +0200 Subject: [PATCH 037/144] Force integer division --- .../plugins/publish/submit_maya_deadline.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 0a18506bd4..ac9d5a3d79 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -117,8 +117,8 @@ def _format_tiles( tile = 0 out = {"JobInfo": {}, "PluginInfo": {}} cfg = OrderedDict() - w_space = width / tiles_x - h_space = height / tiles_y + w_space = width // tiles_x + h_space = height // tiles_y cfg["TilesCropped"] = "False" @@ -146,16 +146,16 @@ def _format_tiles( left = (tile_x - 1) * w_space right = (tile_x * w_space) - 1 - out["PluginInfo"]["RegionTop{}".format(tile)] = int(top) - out["PluginInfo"]["RegionBottom{}".format(tile)] = int(bottom) - out["PluginInfo"]["RegionLeft{}".format(tile)] = int(left) - out["PluginInfo"]["RegionRight{}".format(tile)] = int(right) + out["PluginInfo"]["RegionTop{}".format(tile)] = top + out["PluginInfo"]["RegionBottom{}".format(tile)] = bottom + out["PluginInfo"]["RegionLeft{}".format(tile)] = left + out["PluginInfo"]["RegionRight{}".format(tile)] = right cfg["Tile{}".format(tile)] = new_filename cfg["Tile{}Tile".format(tile)] = new_filename cfg["Tile{}FileName".format(tile)] = new_filename - cfg["Tile{}X".format(tile)] = int(left) - cfg["Tile{}Y".format(tile)] = int(top) + cfg["Tile{}X".format(tile)] = left + cfg["Tile{}Y".format(tile)] = top cfg["Tile{}Width".format(tile)] = w_space cfg["Tile{}Height".format(tile)] = h_space From 67ee5b5710548a0d390d9aa34b13563da7bda30d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 31 Aug 2022 10:35:52 +0200 Subject: [PATCH 038/144] Format with signed numbers (include + or -) This allows negative offsets to be pasted --- .../plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py b/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py index 9fca1b5391..05899de5e1 100644 --- a/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py +++ b/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py @@ -453,7 +453,7 @@ class OpenPypeTileAssembler(DeadlinePlugin): # Swap to have input as foreground args.append("--swap") # Paste foreground to background - args.append("--paste +{}+{}".format(pos_x, pos_y)) + args.append("--paste {x:+d}{y:+d}".format(x=pos_x, y=pos_y)) args.append("-o") args.append(output_path) From 1da0f46930dacb69ff13cb0984ebfb2b341c6ceb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 31 Aug 2022 10:58:41 +0200 Subject: [PATCH 039/144] Fix docstring typos --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index ac9d5a3d79..e77c86ec43 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -103,13 +103,13 @@ def _format_tiles( filename (str): Filename to process as tiles. index (int): Index of that file if it is sequence. tiles_x (int): Number of tiles in X. - tiles_y (int): Number if tikes in Y. + tiles_y (int): Number of tiles in Y. width (int): Width resolution of final image. height (int): Height resolution of final image. prefix (str): Image prefix. Returns: - (dict, dict): Tuple of two dictionaires - first can be used to + (dict, dict): Tuple of two dictionaries - first can be used to extend JobInfo, second has tiles x, y, width and height used for assembler configuration. From b939556394fa1456355fe952493cbc13e1a2735d Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 12:04:40 +0200 Subject: [PATCH 040/144] Use new import source of Extractor --- .../hiero/plugins/publish/extract_clip_effects.py | 5 +++-- .../hosts/hiero/plugins/publish/extract_frames.py | 13 +++++++++---- .../hiero/plugins/publish/extract_thumbnail.py | 5 +++-- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/openpype/hosts/hiero/plugins/publish/extract_clip_effects.py b/openpype/hosts/hiero/plugins/publish/extract_clip_effects.py index 5b0aa270a7..7fb381ff7e 100644 --- a/openpype/hosts/hiero/plugins/publish/extract_clip_effects.py +++ b/openpype/hosts/hiero/plugins/publish/extract_clip_effects.py @@ -2,10 +2,11 @@ import os import json import pyblish.api -import openpype + +from openpype.pipeline import publish -class ExtractClipEffects(openpype.api.Extractor): +class ExtractClipEffects(publish.Extractor): """Extract clip effects instances.""" order = pyblish.api.ExtractorOrder diff --git a/openpype/hosts/hiero/plugins/publish/extract_frames.py b/openpype/hosts/hiero/plugins/publish/extract_frames.py index aa3eda2e9f..f865d2fb39 100644 --- a/openpype/hosts/hiero/plugins/publish/extract_frames.py +++ b/openpype/hosts/hiero/plugins/publish/extract_frames.py @@ -1,9 +1,14 @@ import os import pyblish.api -import openpype + +from openpype.lib import ( + get_oiio_tools_path, + run_subprocess, +) +from openpype.pipeline import publish -class ExtractFrames(openpype.api.Extractor): +class ExtractFrames(publish.Extractor): """Extracts frames""" order = pyblish.api.ExtractorOrder @@ -13,7 +18,7 @@ class ExtractFrames(openpype.api.Extractor): movie_extensions = ["mov", "mp4"] def process(self, instance): - oiio_tool_path = openpype.lib.get_oiio_tools_path() + oiio_tool_path = get_oiio_tools_path() staging_dir = self.staging_dir(instance) output_template = os.path.join(staging_dir, instance.data["name"]) sequence = instance.context.data["activeTimeline"] @@ -43,7 +48,7 @@ class ExtractFrames(openpype.api.Extractor): args.extend(["--powc", "0.45,0.45,0.45,1.0"]) args.extend([input_path, "-o", output_path]) - output = openpype.api.run_subprocess(args) + output = run_subprocess(args) failed_output = "oiiotool produced no output." if failed_output in output: diff --git a/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py b/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py index d12e7665bf..e64aa89b26 100644 --- a/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py @@ -1,9 +1,10 @@ import os import pyblish.api -import openpype.api + +from openpype.pipeline import publish -class ExtractThumnail(openpype.api.Extractor): +class ExtractThumnail(publish.Extractor): """ Extractor for track item's tumnails """ From b61688828e4e65433e7d7aabb68883601b62244a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 12:11:07 +0200 Subject: [PATCH 041/144] Use new import source of Extractor --- .../hosts/maya/plugins/publish/extract_ass.py | 6 ++--- .../maya/plugins/publish/extract_assembly.py | 7 +++-- .../maya/plugins/publish/extract_assproxy.py | 6 ++--- .../plugins/publish/extract_camera_alembic.py | 4 +-- .../publish/extract_camera_mayaScene.py | 4 +-- .../hosts/maya/plugins/publish/extract_fbx.py | 6 ++--- .../maya/plugins/publish/extract_layout.py | 27 ++++++++++--------- .../maya/plugins/publish/extract_look.py | 8 +++--- .../plugins/publish/extract_maya_scene_raw.py | 5 ++-- .../maya/plugins/publish/extract_model.py | 4 +-- .../publish/extract_multiverse_look.py | 4 +-- .../plugins/publish/extract_multiverse_usd.py | 4 +-- .../publish/extract_multiverse_usd_comp.py | 4 +-- .../publish/extract_multiverse_usd_over.py | 4 +-- .../maya/plugins/publish/extract_playblast.py | 6 ++--- .../plugins/publish/extract_pointcache.py | 4 +-- .../plugins/publish/extract_redshift_proxy.py | 4 +-- .../plugins/publish/extract_rendersetup.py | 7 ++--- .../hosts/maya/plugins/publish/extract_rig.py | 4 +-- .../maya/plugins/publish/extract_thumbnail.py | 4 +-- .../publish/extract_unreal_skeletalmesh.py | 5 ++-- .../publish/extract_unreal_staticmesh.py | 5 ++-- .../maya/plugins/publish/extract_vrayproxy.py | 4 +-- .../maya/plugins/publish/extract_vrayscene.py | 4 +-- .../plugins/publish/extract_xgen_cache.py | 4 +-- .../plugins/publish/extract_yeti_cache.py | 4 +-- .../maya/plugins/publish/extract_yeti_rig.py | 4 +-- 27 files changed, 76 insertions(+), 76 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_ass.py b/openpype/hosts/maya/plugins/publish/extract_ass.py index 760f410f91..5c21a4ff08 100644 --- a/openpype/hosts/maya/plugins/publish/extract_ass.py +++ b/openpype/hosts/maya/plugins/publish/extract_ass.py @@ -1,12 +1,12 @@ import os -import openpype.api - from maya import cmds + +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection -class ExtractAssStandin(openpype.api.Extractor): +class ExtractAssStandin(publish.Extractor): """Extract the content of the instance to a ass file Things to pay attention to: diff --git a/openpype/hosts/maya/plugins/publish/extract_assembly.py b/openpype/hosts/maya/plugins/publish/extract_assembly.py index 482930b76e..466fe962ab 100644 --- a/openpype/hosts/maya/plugins/publish/extract_assembly.py +++ b/openpype/hosts/maya/plugins/publish/extract_assembly.py @@ -1,14 +1,13 @@ +import os import json -import os - -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import extract_alembic from maya import cmds -class ExtractAssembly(openpype.api.Extractor): +class ExtractAssembly(publish.Extractor): """Produce an alembic of just point positions and normals. Positions and normals are preserved, but nothing more, diff --git a/openpype/hosts/maya/plugins/publish/extract_assproxy.py b/openpype/hosts/maya/plugins/publish/extract_assproxy.py index 93720dbb82..4937a28a9e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_assproxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_assproxy.py @@ -3,17 +3,17 @@ import contextlib from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection -class ExtractAssProxy(openpype.api.Extractor): +class ExtractAssProxy(publish.Extractor): """Extract proxy model as Maya Ascii to use as arnold standin """ - order = openpype.api.Extractor.order + 0.2 + order = publish.Extractor.order + 0.2 label = "Ass Proxy (Maya ASCII)" hosts = ["maya"] families = ["ass"] diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py index b744bfd0fe..aa445a0387 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_alembic.py @@ -2,11 +2,11 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api import lib -class ExtractCameraAlembic(openpype.api.Extractor): +class ExtractCameraAlembic(publish.Extractor): """Extract a Camera as Alembic. The cameras gets baked to world space by default. Only when the instance's diff --git a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py index 8d6c4b5f3c..7467fa027d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py +++ b/openpype/hosts/maya/plugins/publish/extract_camera_mayaScene.py @@ -5,7 +5,7 @@ import itertools from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api import lib @@ -78,7 +78,7 @@ def unlock(plug): cmds.disconnectAttr(source, destination) -class ExtractCameraMayaScene(openpype.api.Extractor): +class ExtractCameraMayaScene(publish.Extractor): """Extract a Camera as Maya Scene. This will create a duplicate of the camera that will be baked *with* diff --git a/openpype/hosts/maya/plugins/publish/extract_fbx.py b/openpype/hosts/maya/plugins/publish/extract_fbx.py index fbbe8e06b0..9af3acef65 100644 --- a/openpype/hosts/maya/plugins/publish/extract_fbx.py +++ b/openpype/hosts/maya/plugins/publish/extract_fbx.py @@ -4,13 +4,13 @@ import os from maya import cmds # noqa import maya.mel as mel # noqa import pyblish.api -import openpype.api -from openpype.hosts.maya.api.lib import maintained_selection +from openpype.pipeline import publish +from openpype.hosts.maya.api.lib import maintained_selection from openpype.hosts.maya.api import fbx -class ExtractFBX(openpype.api.Extractor): +class ExtractFBX(publish.Extractor): """Extract FBX from Maya. This extracts reproducible FBX exports ignoring any of the diff --git a/openpype/hosts/maya/plugins/publish/extract_layout.py b/openpype/hosts/maya/plugins/publish/extract_layout.py index 991217684a..0f499b09b1 100644 --- a/openpype/hosts/maya/plugins/publish/extract_layout.py +++ b/openpype/hosts/maya/plugins/publish/extract_layout.py @@ -5,13 +5,11 @@ import json from maya import cmds from maya.api import OpenMaya as om -from bson.objectid import ObjectId - -from openpype.pipeline import legacy_io -import openpype.api +from openpype.client import get_representation_by_id +from openpype.pipeline import legacy_io, publish -class ExtractLayout(openpype.api.Extractor): +class ExtractLayout(publish.Extractor): """Extract a layout.""" label = "Extract Layout" @@ -30,6 +28,8 @@ class ExtractLayout(openpype.api.Extractor): instance.data["representations"] = [] json_data = [] + # TODO representation queries can be refactored to be faster + project_name = legacy_io.active_project() for asset in cmds.sets(str(instance), query=True): # Find the container @@ -43,11 +43,11 @@ class ExtractLayout(openpype.api.Extractor): representation_id = cmds.getAttr(f"{container}.representation") - representation = legacy_io.find_one( - { - "type": "representation", - "_id": ObjectId(representation_id) - }, projection={"parent": True, "context.family": True}) + representation = get_representation_by_id( + project_name, + representation_id, + fields=["parent", "context.family"] + ) self.log.info(representation) @@ -102,9 +102,10 @@ class ExtractLayout(openpype.api.Extractor): for i in range(0, len(t_matrix_list), row_length): t_matrix.append(t_matrix_list[i:i + row_length]) - json_element["transform_matrix"] = [] - for row in t_matrix: - json_element["transform_matrix"].append(list(row)) + json_element["transform_matrix"] = [ + list(row) + for row in t_matrix + ] basis_list = [ 1, 0, 0, 0, diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index ce3b265566..91b0da75c6 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -13,8 +13,8 @@ from maya import cmds # noqa import pyblish.api -import openpype.api -from openpype.pipeline import legacy_io +from openpype.lib import source_hash +from openpype.pipeline import legacy_io, publish from openpype.hosts.maya.api import lib # Modes for transfer @@ -161,7 +161,7 @@ def no_workspace_dir(): os.rmdir(fake_workspace_dir) -class ExtractLook(openpype.api.Extractor): +class ExtractLook(publish.Extractor): """Extract Look (Maya Scene + JSON) Only extracts the sets (shadingEngines and alike) alongside a .json file @@ -505,7 +505,7 @@ class ExtractLook(openpype.api.Extractor): args = [] if do_maketx: args.append("maketx") - texture_hash = openpype.api.source_hash(filepath, *args) + texture_hash = source_hash(filepath, *args) # If source has been published before with the same settings, # then don't reprocess but hardlink from the original diff --git a/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py index 3a47cdadb5..3769ec3605 100644 --- a/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py +++ b/openpype/hosts/maya/plugins/publish/extract_maya_scene_raw.py @@ -4,12 +4,11 @@ import os from maya import cmds -import openpype.api from openpype.hosts.maya.api.lib import maintained_selection -from openpype.pipeline import AVALON_CONTAINER_ID +from openpype.pipeline import AVALON_CONTAINER_ID, publish -class ExtractMayaSceneRaw(openpype.api.Extractor): +class ExtractMayaSceneRaw(publish.Extractor): """Extract as Maya Scene (raw). This will preserve all references, construction history, etc. diff --git a/openpype/hosts/maya/plugins/publish/extract_model.py b/openpype/hosts/maya/plugins/publish/extract_model.py index 0282d1e9c8..7c8c3a2981 100644 --- a/openpype/hosts/maya/plugins/publish/extract_model.py +++ b/openpype/hosts/maya/plugins/publish/extract_model.py @@ -4,11 +4,11 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api import lib -class ExtractModel(openpype.api.Extractor): +class ExtractModel(publish.Extractor): """Extract as Model (Maya Scene). Only extracts contents based on the original "setMembers" data to ensure diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py index 82e2b41929..92137acb95 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_look.py @@ -2,11 +2,11 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection -class ExtractMultiverseLook(openpype.api.Extractor): +class ExtractMultiverseLook(publish.Extractor): """Extractor for Multiverse USD look data. This will extract: diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py index 3654be7b34..6c352bebe6 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd.py @@ -3,11 +3,11 @@ import six from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection -class ExtractMultiverseUsd(openpype.api.Extractor): +class ExtractMultiverseUsd(publish.Extractor): """Extractor for Multiverse USD Asset data. This will extract settings for a Multiverse Write Asset operation: diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py index ad9303657f..a62729c198 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_comp.py @@ -2,11 +2,11 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection -class ExtractMultiverseUsdComposition(openpype.api.Extractor): +class ExtractMultiverseUsdComposition(publish.Extractor): """Extractor of Multiverse USD Composition data. This will extract settings for a Multiverse Write Composition operation: diff --git a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py index d44e3878b8..0628623e88 100644 --- a/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py +++ b/openpype/hosts/maya/plugins/publish/extract_multiverse_usd_over.py @@ -1,12 +1,12 @@ import os -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection from maya import cmds -class ExtractMultiverseUsdOverride(openpype.api.Extractor): +class ExtractMultiverseUsdOverride(publish.Extractor): """Extractor for Multiverse USD Override data. This will extract settings for a Multiverse Write Override operation: diff --git a/openpype/hosts/maya/plugins/publish/extract_playblast.py b/openpype/hosts/maya/plugins/publish/extract_playblast.py index 871adda0c3..81fdba2f98 100644 --- a/openpype/hosts/maya/plugins/publish/extract_playblast.py +++ b/openpype/hosts/maya/plugins/publish/extract_playblast.py @@ -1,18 +1,16 @@ import os -import glob -import contextlib import clique import capture +from openpype.pipeline import publish from openpype.hosts.maya.api import lib -import openpype.api from maya import cmds import pymel.core as pm -class ExtractPlayblast(openpype.api.Extractor): +class ExtractPlayblast(publish.Extractor): """Extract viewport playblast. Takes review camera and creates review Quicktime video based on viewport diff --git a/openpype/hosts/maya/plugins/publish/extract_pointcache.py b/openpype/hosts/maya/plugins/publish/extract_pointcache.py index bf6feecef3..7c1c6d5c12 100644 --- a/openpype/hosts/maya/plugins/publish/extract_pointcache.py +++ b/openpype/hosts/maya/plugins/publish/extract_pointcache.py @@ -2,7 +2,7 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import ( extract_alembic, suspended_refresh, @@ -11,7 +11,7 @@ from openpype.hosts.maya.api.lib import ( ) -class ExtractAlembic(openpype.api.Extractor): +class ExtractAlembic(publish.Extractor): """Produce an alembic of just point positions and normals. Positions and normals, uvs, creases are preserved, but nothing more, diff --git a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py index 23cac9190d..4377275635 100644 --- a/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_redshift_proxy.py @@ -4,11 +4,11 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection -class ExtractRedshiftProxy(openpype.api.Extractor): +class ExtractRedshiftProxy(publish.Extractor): """Extract the content of the instance to a redshift proxy file.""" label = "Redshift Proxy (.rs)" diff --git a/openpype/hosts/maya/plugins/publish/extract_rendersetup.py b/openpype/hosts/maya/plugins/publish/extract_rendersetup.py index 6bdd5f590e..5970c038a4 100644 --- a/openpype/hosts/maya/plugins/publish/extract_rendersetup.py +++ b/openpype/hosts/maya/plugins/publish/extract_rendersetup.py @@ -1,10 +1,11 @@ -import json import os -import openpype.api +import json + import maya.app.renderSetup.model.renderSetup as renderSetup +from openpype.pipeline import publish -class ExtractRenderSetup(openpype.api.Extractor): +class ExtractRenderSetup(publish.Extractor): """ Produce renderSetup template file diff --git a/openpype/hosts/maya/plugins/publish/extract_rig.py b/openpype/hosts/maya/plugins/publish/extract_rig.py index 53c1eeb671..c71a2f710d 100644 --- a/openpype/hosts/maya/plugins/publish/extract_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_rig.py @@ -4,11 +4,11 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection -class ExtractRig(openpype.api.Extractor): +class ExtractRig(publish.Extractor): """Extract rig as Maya Scene.""" label = "Extract Rig (Maya Scene)" diff --git a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py index 9380da5128..854301ea48 100644 --- a/openpype/hosts/maya/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/maya/plugins/publish/extract_thumbnail.py @@ -3,14 +3,14 @@ import glob import capture +from openpype.pipeline import publish from openpype.hosts.maya.api import lib -import openpype.api from maya import cmds import pymel.core as pm -class ExtractThumbnail(openpype.api.Extractor): +class ExtractThumbnail(publish.Extractor): """Extract viewport thumbnail. Takes review camera and creates a thumbnail based on viewport diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py index 7ef7f2f181..258120db2f 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_skeletalmesh.py @@ -6,7 +6,8 @@ from contextlib import contextmanager from maya import cmds # noqa import pyblish.api -import openpype.api + +from openpype.pipeline import publish from openpype.hosts.maya.api import fbx @@ -20,7 +21,7 @@ def renamed(original_name, renamed_name): cmds.rename(renamed_name, original_name) -class ExtractUnrealSkeletalMesh(openpype.api.Extractor): +class ExtractUnrealSkeletalMesh(publish.Extractor): """Extract Unreal Skeletal Mesh as FBX from Maya. """ order = pyblish.api.ExtractorOrder - 0.1 diff --git a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py index 69d51f9ff1..44f0615a27 100644 --- a/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py +++ b/openpype/hosts/maya/plugins/publish/extract_unreal_staticmesh.py @@ -5,7 +5,8 @@ import os from maya import cmds # noqa import pyblish.api -import openpype.api + +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import ( parent_nodes, maintained_selection @@ -13,7 +14,7 @@ from openpype.hosts.maya.api.lib import ( from openpype.hosts.maya.api import fbx -class ExtractUnrealStaticMesh(openpype.api.Extractor): +class ExtractUnrealStaticMesh(publish.Extractor): """Extract Unreal Static Mesh as FBX from Maya. """ order = pyblish.api.ExtractorOrder - 0.1 diff --git a/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py b/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py index 562ca078e1..38bf02245a 100644 --- a/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py +++ b/openpype/hosts/maya/plugins/publish/extract_vrayproxy.py @@ -2,11 +2,11 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import maintained_selection -class ExtractVRayProxy(openpype.api.Extractor): +class ExtractVRayProxy(publish.Extractor): """Extract the content of the instance to a vrmesh file Things to pay attention to: diff --git a/openpype/hosts/maya/plugins/publish/extract_vrayscene.py b/openpype/hosts/maya/plugins/publish/extract_vrayscene.py index 5d41697e5f..8442df1611 100644 --- a/openpype/hosts/maya/plugins/publish/extract_vrayscene.py +++ b/openpype/hosts/maya/plugins/publish/extract_vrayscene.py @@ -3,14 +3,14 @@ import os import re -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.render_setup_tools import export_in_rs_layer from openpype.hosts.maya.api.lib import maintained_selection from maya import cmds -class ExtractVrayscene(openpype.api.Extractor): +class ExtractVrayscene(publish.Extractor): """Extractor for vrscene.""" label = "VRay Scene (.vrscene)" diff --git a/openpype/hosts/maya/plugins/publish/extract_xgen_cache.py b/openpype/hosts/maya/plugins/publish/extract_xgen_cache.py index 5728682abe..77350f343e 100644 --- a/openpype/hosts/maya/plugins/publish/extract_xgen_cache.py +++ b/openpype/hosts/maya/plugins/publish/extract_xgen_cache.py @@ -2,14 +2,14 @@ import os from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api.lib import ( suspended_refresh, maintained_selection ) -class ExtractXgenCache(openpype.api.Extractor): +class ExtractXgenCache(publish.Extractor): """Produce an alembic of just xgen interactive groom """ diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py index cf6db00e9a..b61f599cab 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_cache.py @@ -3,10 +3,10 @@ import json from maya import cmds -import openpype.api +from openpype.pipeline import publish -class ExtractYetiCache(openpype.api.Extractor): +class ExtractYetiCache(publish.Extractor): """Producing Yeti cache files using scene time range. This will extract Yeti cache file sequence and fur settings. diff --git a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py index 6e21bffa4e..1d0c5e88c3 100644 --- a/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py +++ b/openpype/hosts/maya/plugins/publish/extract_yeti_rig.py @@ -7,7 +7,7 @@ import contextlib from maya import cmds -import openpype.api +from openpype.pipeline import publish from openpype.hosts.maya.api import lib @@ -90,7 +90,7 @@ def yetigraph_attribute_values(assumed_destination, resources): pass -class ExtractYetiRig(openpype.api.Extractor): +class ExtractYetiRig(publish.Extractor): """Extract the Yeti rig to a Maya Scene and write the Yeti rig data.""" label = "Extract Yeti Rig" From da8697d8821d9396d4a401e80be1ae6cdeb460b6 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 1 Sep 2022 12:52:30 +0200 Subject: [PATCH 042/144] Use new import source of Extractor --- .../hosts/nuke/plugins/publish/extract_backdrop.py | 4 ++-- openpype/hosts/nuke/plugins/publish/extract_camera.py | 5 +++-- openpype/hosts/nuke/plugins/publish/extract_gizmo.py | 4 ++-- openpype/hosts/nuke/plugins/publish/extract_model.py | 5 +++-- .../hosts/nuke/plugins/publish/extract_render_local.py | 10 ++++++---- .../hosts/nuke/plugins/publish/extract_review_data.py | 7 ++++--- .../nuke/plugins/publish/extract_review_data_lut.py | 5 +++-- .../nuke/plugins/publish/extract_review_data_mov.py | 7 ++++--- .../hosts/nuke/plugins/publish/extract_slate_frame.py | 4 ++-- .../hosts/nuke/plugins/publish/extract_thumbnail.py | 5 +++-- 10 files changed, 32 insertions(+), 24 deletions(-) diff --git a/openpype/hosts/nuke/plugins/publish/extract_backdrop.py b/openpype/hosts/nuke/plugins/publish/extract_backdrop.py index 0a2df0898e..d1e5c4cc5a 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_backdrop.py +++ b/openpype/hosts/nuke/plugins/publish/extract_backdrop.py @@ -4,7 +4,7 @@ import nuke import pyblish.api -import openpype +from openpype.pipeline import publish from openpype.hosts.nuke.api.lib import ( maintained_selection, reset_selection, @@ -12,7 +12,7 @@ from openpype.hosts.nuke.api.lib import ( ) -class ExtractBackdropNode(openpype.api.Extractor): +class ExtractBackdropNode(publish.Extractor): """Extracting content of backdrop nodes Will create nuke script only with containing nodes. diff --git a/openpype/hosts/nuke/plugins/publish/extract_camera.py b/openpype/hosts/nuke/plugins/publish/extract_camera.py index 54f65a0be3..b751bfab03 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_camera.py +++ b/openpype/hosts/nuke/plugins/publish/extract_camera.py @@ -5,11 +5,12 @@ from pprint import pformat import nuke import pyblish.api -import openpype.api + +from openpype.pipeline import publish from openpype.hosts.nuke.api.lib import maintained_selection -class ExtractCamera(openpype.api.Extractor): +class ExtractCamera(publish.Extractor): """ 3D camera exctractor """ label = 'Exctract Camera' diff --git a/openpype/hosts/nuke/plugins/publish/extract_gizmo.py b/openpype/hosts/nuke/plugins/publish/extract_gizmo.py index 2d5bfdeb5e..3047ad6724 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_gizmo.py +++ b/openpype/hosts/nuke/plugins/publish/extract_gizmo.py @@ -3,7 +3,7 @@ import nuke import pyblish.api -import openpype +from openpype.pipeline import publish from openpype.hosts.nuke.api import utils as pnutils from openpype.hosts.nuke.api.lib import ( maintained_selection, @@ -12,7 +12,7 @@ from openpype.hosts.nuke.api.lib import ( ) -class ExtractGizmo(openpype.api.Extractor): +class ExtractGizmo(publish.Extractor): """Extracting Gizmo (Group) node Will create nuke script only with the Gizmo node. diff --git a/openpype/hosts/nuke/plugins/publish/extract_model.py b/openpype/hosts/nuke/plugins/publish/extract_model.py index 0375263338..d82cb3110b 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_model.py +++ b/openpype/hosts/nuke/plugins/publish/extract_model.py @@ -2,14 +2,15 @@ import os from pprint import pformat import nuke import pyblish.api -import openpype.api + +from openpype.pipeline import publish from openpype.hosts.nuke.api.lib import ( maintained_selection, select_nodes ) -class ExtractModel(openpype.api.Extractor): +class ExtractModel(publish.Extractor): """ 3D model exctractor """ label = 'Exctract Model' diff --git a/openpype/hosts/nuke/plugins/publish/extract_render_local.py b/openpype/hosts/nuke/plugins/publish/extract_render_local.py index 8879f0c999..843d588786 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_render_local.py +++ b/openpype/hosts/nuke/plugins/publish/extract_render_local.py @@ -1,11 +1,13 @@ -import pyblish.api -import nuke import os -import openpype + +import pyblish.api import clique +import nuke + +from openpype.pipeline import publish -class NukeRenderLocal(openpype.api.Extractor): +class NukeRenderLocal(publish.Extractor): # TODO: rewrite docstring to nuke """Render the current Nuke composition locally. diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data.py b/openpype/hosts/nuke/plugins/publish/extract_review_data.py index 38a8140cff..3c85b21b08 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data.py @@ -1,10 +1,11 @@ import os -import pyblish.api -import openpype from pprint import pformat +import pyblish.api + +from openpype.pipeline import publish -class ExtractReviewData(openpype.api.Extractor): +class ExtractReviewData(publish.Extractor): """Extracts review tag into available representation """ diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py index 4cf2fd7d9f..67779e9599 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_lut.py @@ -1,11 +1,12 @@ import os import pyblish.api -import openpype + +from openpype.pipeline import publish from openpype.hosts.nuke.api import plugin from openpype.hosts.nuke.api.lib import maintained_selection -class ExtractReviewDataLut(openpype.api.Extractor): +class ExtractReviewDataLut(publish.Extractor): """Extracts movie and thumbnail with baked in luts must be run after extract_render_local.py diff --git a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py index fc16e189fb..3fcfc2a4b5 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py +++ b/openpype/hosts/nuke/plugins/publish/extract_review_data_mov.py @@ -1,13 +1,14 @@ import os -from pprint import pformat import re +from pprint import pformat import pyblish.api -import openpype + +from openpype.pipeline import publish from openpype.hosts.nuke.api import plugin from openpype.hosts.nuke.api.lib import maintained_selection -class ExtractReviewDataMov(openpype.api.Extractor): +class ExtractReviewDataMov(publish.Extractor): """Extracts movie and thumbnail with baked in luts must be run after extract_render_local.py diff --git a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py index b5cad143db..e7197b4fa8 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py +++ b/openpype/hosts/nuke/plugins/publish/extract_slate_frame.py @@ -6,7 +6,7 @@ import copy import pyblish.api import six -import openpype +from openpype.pipeline import publish from openpype.hosts.nuke.api import ( maintained_selection, duplicate_node, @@ -14,7 +14,7 @@ from openpype.hosts.nuke.api import ( ) -class ExtractSlateFrame(openpype.api.Extractor): +class ExtractSlateFrame(publish.Extractor): """Extracts movie and thumbnail with baked in luts must be run after extract_render_local.py diff --git a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py index 2a919051d2..19eae9638b 100644 --- a/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/nuke/plugins/publish/extract_thumbnail.py @@ -2,7 +2,8 @@ import sys import os import nuke import pyblish.api -import openpype + +from openpype.pipeline import publish from openpype.hosts.nuke.api import ( maintained_selection, get_view_process_node @@ -13,7 +14,7 @@ if sys.version_info[0] >= 3: unicode = str -class ExtractThumbnail(openpype.api.Extractor): +class ExtractThumbnail(publish.Extractor): """Extracts movie and thumbnail with baked in luts must be run after extract_render_local.py From db1fa6d40ef59f9e3061a637a04874d4857a6585 Mon Sep 17 00:00:00 2001 From: Thomas Fricard Date: Mon, 5 Sep 2022 12:49:43 +0200 Subject: [PATCH 043/144] add a python2 compatibility for the FileNotFoundError --- openpype/hosts/houdini/api/shelves.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/hosts/houdini/api/shelves.py b/openpype/hosts/houdini/api/shelves.py index 805ce4c397..248d99105c 100644 --- a/openpype/hosts/houdini/api/shelves.py +++ b/openpype/hosts/houdini/api/shelves.py @@ -1,6 +1,7 @@ import os import logging import platform +import six from openpype.settings import get_project_settings @@ -8,6 +9,9 @@ import hou log = logging.getLogger("openpype.hosts.houdini.shelves") +if six.PY2: + FileNotFoundError = IOError + def generate_shelves(): """This function generates complete shelves from shelf set to tools From 1c7f32e93a750cd3f97b17fb3fba53054a6e74f5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 8 Sep 2022 17:43:01 +0800 Subject: [PATCH 044/144] adding lock task workfiles when users are working on them --- openpype/hosts/maya/api/pipeline.py | 99 ++++++++++++++++++++- openpype/pipeline/workfile/lock_workfile.py | 67 ++++++++++++++ openpype/tools/workfiles/files_widget.py | 15 ++++ 3 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 openpype/pipeline/workfile/lock_workfile.py diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index c963b5d996..b645b41fa0 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -31,6 +31,12 @@ from openpype.pipeline import ( AVALON_CONTAINER_ID, ) from openpype.pipeline.load import any_outdated_containers +from openpype.pipeline.workfile.lock_workfile import ( + create_workfile_lock, + get_username, + remove_workfile_lock, + is_workfile_locked +) from openpype.hosts.maya import MAYA_ROOT_DIR from openpype.hosts.maya.lib import copy_workspace_mel @@ -99,7 +105,10 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): register_event_callback("open", on_open) register_event_callback("new", on_new) register_event_callback("before.save", on_before_save) + register_event_callback("before.close", on_before_close) + register_event_callback("before.file.open", before_file_open) register_event_callback("taskChanged", on_task_changed) + register_event_callback("workfile.open.before", before_workfile_open) register_event_callback("workfile.save.before", before_workfile_save) def open_workfile(self, filepath): @@ -161,8 +170,25 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): ) ) - self._op_events[_on_scene_open] = OpenMaya.MSceneMessage.addCallback( - OpenMaya.MSceneMessage.kAfterOpen, _on_scene_open + self._op_events[_on_scene_open] = ( + OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kAfterOpen, + _on_scene_open + ) + ) + + self._op_events[_before_scene_open] = ( + OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kBeforeOpen, + _before_scene_open + ) + ) + + self._op_events[_before_close_maya] = ( + OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kMayaExiting, + _before_close_maya + ) ) self.log.info("Installed event handler _on_scene_save..") @@ -170,6 +196,8 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): self.log.info("Installed event handler _on_scene_new..") self.log.info("Installed event handler _on_maya_initialized..") self.log.info("Installed event handler _on_scene_open..") + self.log.info("Installed event handler _check_lock_file..") + self.log.info("Installed event handler _before_close_maya..") def _set_project(): @@ -216,6 +244,14 @@ def _on_scene_open(*args): emit_event("open") +def _before_close_maya(*args): + emit_event("before.close") + + +def _before_scene_open(*args): + emit_event("before.file.open") + + def _before_scene_save(return_code, client_data): # Default to allowing the action. Registered @@ -229,6 +265,12 @@ def _before_scene_save(return_code, client_data): ) +def _remove_workfile_lock(): + filepath = current_file() + if filepath: + remove_workfile_lock(filepath) + + def uninstall(): pyblish.api.deregister_plugin_path(PUBLISH_PATH) pyblish.api.deregister_host("mayabatch") @@ -426,6 +468,49 @@ def on_before_save(): return lib.validate_fps() +def after_file_open(): + """Check if there is a user opening the file""" + log.info("Running callback on checking the lock file...") + + # add the lock file when opening the file + filepath = current_file() + + if not is_workfile_locked(filepath): + create_workfile_lock(filepath) + + else: + username = get_username(filepath) + reminder = cmds.window(title="Reminder", width=400, height=30) + cmds.columnLayout(adjustableColumn=True) + cmds.separator() + cmds.columnLayout(adjustableColumn=True) + comment = " %s is working the same workfile!" % username + cmds.text(comment, align='center') + cmds.text(vis=False) + cmds.rowColumnLayout(numberOfColumns=3, + columnWidth=[(1, 300), (2, 100)], + columnSpacing=[(2, 10)]) + cmds.separator(vis=False) + quit_command = "cmds.quit(force=True);cmds.deleteUI('%s')" % reminder + cmds.button(label='Ok', command=quit_command) + cmds.showWindow(reminder) + + +def on_before_close(): + """Delete the lock file after user quitting the Maya Scene""" + log.info("Closing Maya...") + # delete the lock file + filepath = current_file() + remove_workfile_lock(filepath) + + +def before_file_open(): + """check lock file when the file changed""" + log.info("check lock file when file changed...") + # delete the lock file + _remove_workfile_lock() + + def on_save(): """Automatically add IDs to new nodes @@ -434,6 +519,8 @@ def on_save(): """ log.info("Running callback on save..") + # remove lockfile if users jumps over from one scene to another + _remove_workfile_lock() # # Update current task for the current scene # update_task_from_path(cmds.file(query=True, sceneName=True)) @@ -491,6 +578,9 @@ def on_open(): dialog.on_clicked.connect(_on_show_inventory) dialog.show() + # create lock file for the maya scene + after_file_open() + def on_new(): """Set project resolution and fps when create a new file""" @@ -544,7 +634,12 @@ def on_task_changed(): ) +def before_workfile_open(): + _remove_workfile_lock() + + def before_workfile_save(event): + _remove_workfile_lock() workdir_path = event["workdir_path"] if workdir_path: copy_workspace_mel(workdir_path) diff --git a/openpype/pipeline/workfile/lock_workfile.py b/openpype/pipeline/workfile/lock_workfile.py new file mode 100644 index 0000000000..03dee66d46 --- /dev/null +++ b/openpype/pipeline/workfile/lock_workfile.py @@ -0,0 +1,67 @@ +import os +import json +from uuid import uuid4 +from openpype.lib.pype_info import get_workstation_info + + +def _read_lock_file(lock_filepath): + with open(lock_filepath, "r") as stream: + data = json.load(stream) + return data + + +def _get_lock_file(filepath): + return filepath + ".lock" + + +def is_workfile_locked(filepath): + lock_filepath = _get_lock_file(filepath) + if not os.path.exists(lock_filepath): + return False + return True + + +def is_workfile_locked_for_current_process(filepath): + if not is_workfile_locked(): + return False + + lock_filepath = _get_lock_file(filepath) + process_id = os.environ["OPENPYPE_PROCESS_ID"] + data = _read_lock_file(lock_filepath) + return data["process_id"] == process_id + + +def delete_workfile_lock(filepath): + lock_filepath = _get_lock_file(filepath) + if not os.path.exists(lock_filepath): + return + + if is_workfile_locked_for_current_process(filepath): + os.remove(filepath) + + +def create_workfile_lock(filepath): + lock_filepath = _get_lock_file(filepath) + process_id = os.environ.get("OPENPYPE_PROCESS_ID") + if not process_id: + process_id = str(uuid4()) + os.environ["OPENPYPE_PROCESS_ID"] = process_id + info = get_workstation_info() + info["process_id"] = process_id + with open(lock_filepath, "w") as stream: + json.dump(info, stream) + + +def get_username(filepath): + lock_filepath = _get_lock_file(filepath) + with open(lock_filepath, "r") as stream: + data = json.load(stream) + username = data["username"] + return username + + +def remove_workfile_lock(filepath): + lock_filepath = _get_lock_file(filepath) + if not os.path.exists(lock_filepath): + return + return os.remove(lock_filepath) \ No newline at end of file diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index a5d5b14bb6..6a554efd8b 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -8,6 +8,10 @@ from Qt import QtWidgets, QtCore from openpype.host import IWorkfileHost from openpype.client import get_asset_by_id +from openpype.pipeline.workfile.lock_workfile import ( + is_workfile_locked, + get_username +) from openpype.tools.utils import PlaceholderLineEdit from openpype.tools.utils.delegates import PrettyTimeDelegate from openpype.lib import ( @@ -454,6 +458,17 @@ class FilesWidget(QtWidgets.QWidget): def open_file(self, filepath): host = self.host + if is_workfile_locked(filepath): + username = get_username(filepath) + popup_dialog = QtWidgets.QMessageBox(parent=self) + popup_dialog.setWindowTitle("Warning") + popup_dialog.setText(username + " is using the file") + popup_dialog.setStandardButtons(popup_dialog.Ok) + + result = popup_dialog.exec_() + if result == popup_dialog.Ok: + return False + if isinstance(host, IWorkfileHost): has_unsaved_changes = host.workfile_has_unsaved_changes() else: From 3782ad4782b2044c20ead45c13a6f457d9a332e5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 8 Sep 2022 17:47:31 +0800 Subject: [PATCH 045/144] adding lock task workfiles when users are working on them --- openpype/pipeline/workfile/lock_workfile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pipeline/workfile/lock_workfile.py b/openpype/pipeline/workfile/lock_workfile.py index 03dee66d46..8e75f6fb61 100644 --- a/openpype/pipeline/workfile/lock_workfile.py +++ b/openpype/pipeline/workfile/lock_workfile.py @@ -64,4 +64,4 @@ def remove_workfile_lock(filepath): lock_filepath = _get_lock_file(filepath) if not os.path.exists(lock_filepath): return - return os.remove(lock_filepath) \ No newline at end of file + return os.remove(lock_filepath) From ff149b68215adc8c0211d7248d94b17376496ad1 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 8 Sep 2022 19:42:55 +0200 Subject: [PATCH 046/144] Remove unused import --- openpype/tools/loader/model.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 9d1f1e045c..19b135bfc5 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -14,8 +14,7 @@ from openpype.client import ( get_versions, get_hero_versions, get_version_by_name, - get_representations, - get_representations_parents + get_representations ) from openpype.pipeline import ( registered_host, From dc903c752014348bda4fd4a2a05600ab7ec66f87 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 8 Sep 2022 19:49:17 +0200 Subject: [PATCH 047/144] Store `loaded_in_scene` as `bool` in model --- openpype/tools/loader/model.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 19b135bfc5..8543672617 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -679,9 +679,7 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): data["asset"] = asset_docs_by_id[asset_id]["name"] data["last_version"] = last_version - - loaded = subset_doc["_id"] in subsets_loaded_by_id - data["loaded_in_scene"] = "yes" if loaded else "no" + data["loaded_in_scene"] = subset_doc["_id"] in subsets_loaded_by_id # Sync server data data.update( From 9ebd602a91a21e427cbd063bdd3841a0370d6b22 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Thu, 8 Sep 2022 20:06:34 +0200 Subject: [PATCH 048/144] Add delegate to loaded in scene column for "yes/no" and colorized column --- openpype/tools/loader/widgets.py | 7 ++++++- openpype/tools/utils/delegates.py | 27 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 3c4a89aa0f..e27d7e6a12 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -36,7 +36,8 @@ from openpype.tools.utils import ( ) from openpype.tools.utils.delegates import ( VersionDelegate, - PrettyTimeDelegate + PrettyTimeDelegate, + LoadedInSceneDelegate ) from openpype.tools.utils.widgets import ( OptionalMenu, @@ -234,6 +235,10 @@ class SubsetWidget(QtWidgets.QWidget): column = model.Columns.index("repre_info") view.setItemDelegateForColumn(column, avail_delegate) + loaded_in_scene_delegate = LoadedInSceneDelegate(view) + column = model.Columns.index("loaded_in_scene") + view.setItemDelegateForColumn(column, loaded_in_scene_delegate) + layout = QtWidgets.QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.addLayout(top_bar_layout) diff --git a/openpype/tools/utils/delegates.py b/openpype/tools/utils/delegates.py index d6c2d69e76..3547251282 100644 --- a/openpype/tools/utils/delegates.py +++ b/openpype/tools/utils/delegates.py @@ -291,3 +291,30 @@ class PrettyTimeDelegate(QtWidgets.QStyledItemDelegate): def displayText(self, value, locale): if value is not None: return pretty_timestamp(value) + + +class LoadedInSceneDelegate(QtWidgets.QStyledItemDelegate): + """Delegate for Loaded in Scene state columns. + + Shows "yes" or "no" for True or False values + Colorizes green or dark grey based on True or False values + + """ + + def __init__(self, *args, **kwargs): + super(LoadedInSceneDelegate, self).__init__(*args, **kwargs) + self._colors = { + True: QtGui.QColor(80, 170, 80), + False: QtGui.QColor(90, 90, 90) + } + + def displayText(self, value, locale): + return "yes" if value else "no" + + def initStyleOption(self, option, index): + super(LoadedInSceneDelegate, self).initStyleOption(option, index) + + # Colorize based on value + value = index.data(QtCore.Qt.DisplayRole) + color = self._colors[bool(value)] + option.palette.setBrush(QtGui.QPalette.Text, color) From 26ae84df161344da8e3132f25d4655a470598645 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Sep 2022 13:20:49 +0800 Subject: [PATCH 049/144] adding lock task workfiles when users are working on them --- openpype/hosts/maya/api/pipeline.py | 60 ++++++++++++------- openpype/pipeline/workfile/lock_workfile.py | 60 ++++++++++++------- .../defaults/project_settings/global.json | 3 +- .../schemas/schema_global_tools.json | 25 ++++++++ openpype/tools/workfiles/files_widget.py | 15 ++++- 5 files changed, 116 insertions(+), 47 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index b645b41fa0..67cf80e707 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -33,9 +33,10 @@ from openpype.pipeline import ( from openpype.pipeline.load import any_outdated_containers from openpype.pipeline.workfile.lock_workfile import ( create_workfile_lock, - get_username, + get_user_from_lock, remove_workfile_lock, - is_workfile_locked + is_workfile_locked, + is_workfile_lock_enabled ) from openpype.hosts.maya import MAYA_ROOT_DIR from openpype.hosts.maya.lib import copy_workspace_mel @@ -266,11 +267,21 @@ def _before_scene_save(return_code, client_data): def _remove_workfile_lock(): + """Remove workfile lock on current file""" + if not handle_workfile_locks(): + return filepath = current_file() if filepath: remove_workfile_lock(filepath) +def handle_workfile_locks(): + if lib.IS_HEADLESS: + return False + project_name = legacy_io.active_project() + return is_workfile_lock_enabled(MayaHost.name, project_name) + + def uninstall(): pyblish.api.deregister_plugin_path(PUBLISH_PATH) pyblish.api.deregister_host("mayabatch") @@ -468,8 +479,10 @@ def on_before_save(): return lib.validate_fps() -def after_file_open(): +def check_lock_on_current_file(): """Check if there is a user opening the file""" + if not handle_workfile_locks(): + return log.info("Running callback on checking the lock file...") # add the lock file when opening the file @@ -477,23 +490,25 @@ def after_file_open(): if not is_workfile_locked(filepath): create_workfile_lock(filepath) + return - else: - username = get_username(filepath) - reminder = cmds.window(title="Reminder", width=400, height=30) - cmds.columnLayout(adjustableColumn=True) - cmds.separator() - cmds.columnLayout(adjustableColumn=True) - comment = " %s is working the same workfile!" % username - cmds.text(comment, align='center') - cmds.text(vis=False) - cmds.rowColumnLayout(numberOfColumns=3, - columnWidth=[(1, 300), (2, 100)], - columnSpacing=[(2, 10)]) - cmds.separator(vis=False) - quit_command = "cmds.quit(force=True);cmds.deleteUI('%s')" % reminder - cmds.button(label='Ok', command=quit_command) - cmds.showWindow(reminder) + username = get_user_from_lock(filepath) + reminder = cmds.window(title="Reminder", width=400, height=30) + cmds.columnLayout(adjustableColumn=True) + cmds.separator() + cmds.columnLayout(adjustableColumn=True) + comment = " %s is working the same workfile!" % username + cmds.text(comment, align='center') + cmds.text(vis=False) + cmds.rowColumnLayout(numberOfColumns=3, + columnWidth=[(1,200), (2, 100), (3, 100)], + columnSpacing=[(3, 10)]) + cmds.separator(vis=False) + cancel_command = "cmds.file(new=True);cmds.deleteUI('%s')" % reminder + ignore_command ="cmds.deleteUI('%s')" % reminder + cmds.button(label='Cancel', command=cancel_command) + cmds.button(label = "Ignore", command=ignore_command) + cmds.showWindow(reminder) def on_before_close(): @@ -501,12 +516,13 @@ def on_before_close(): log.info("Closing Maya...") # delete the lock file filepath = current_file() - remove_workfile_lock(filepath) + if handle_workfile_locks(): + remove_workfile_lock(filepath) def before_file_open(): """check lock file when the file changed""" - log.info("check lock file when file changed...") + log.info("Removing lock on current file before scene open...") # delete the lock file _remove_workfile_lock() @@ -579,7 +595,7 @@ def on_open(): dialog.show() # create lock file for the maya scene - after_file_open() + check_lock_on_current_file() def on_new(): diff --git a/openpype/pipeline/workfile/lock_workfile.py b/openpype/pipeline/workfile/lock_workfile.py index 8e75f6fb61..b62f80c507 100644 --- a/openpype/pipeline/workfile/lock_workfile.py +++ b/openpype/pipeline/workfile/lock_workfile.py @@ -1,17 +1,22 @@ import os import json from uuid import uuid4 +from openpype.lib import Logger, filter_profiles from openpype.lib.pype_info import get_workstation_info +from openpype.settings import get_project_settings def _read_lock_file(lock_filepath): + if not os.path.exists(lock_filepath): + log = Logger.get_logger("_read_lock_file") + log.debug("lock file is not created or readable as expected!") with open(lock_filepath, "r") as stream: data = json.load(stream) return data def _get_lock_file(filepath): - return filepath + ".lock" + return filepath + ".oplock" def is_workfile_locked(filepath): @@ -22,46 +27,59 @@ def is_workfile_locked(filepath): def is_workfile_locked_for_current_process(filepath): - if not is_workfile_locked(): + if not is_workfile_locked(filepath): return False lock_filepath = _get_lock_file(filepath) - process_id = os.environ["OPENPYPE_PROCESS_ID"] data = _read_lock_file(lock_filepath) - return data["process_id"] == process_id + return data["process_id"] == _get_process_id() def delete_workfile_lock(filepath): lock_filepath = _get_lock_file(filepath) - if not os.path.exists(lock_filepath): - return - - if is_workfile_locked_for_current_process(filepath): - os.remove(filepath) + if os.path.exists(lock_filepath): + os.remove(lock_filepath) def create_workfile_lock(filepath): lock_filepath = _get_lock_file(filepath) - process_id = os.environ.get("OPENPYPE_PROCESS_ID") - if not process_id: - process_id = str(uuid4()) - os.environ["OPENPYPE_PROCESS_ID"] = process_id info = get_workstation_info() - info["process_id"] = process_id + info["process_id"] = _get_process_id() with open(lock_filepath, "w") as stream: json.dump(info, stream) -def get_username(filepath): +def get_user_from_lock(filepath): lock_filepath = _get_lock_file(filepath) - with open(lock_filepath, "r") as stream: - data = json.load(stream) + if not os.path.exists(lock_filepath): + return + data = _read_lock_file(lock_filepath) username = data["username"] return username def remove_workfile_lock(filepath): - lock_filepath = _get_lock_file(filepath) - if not os.path.exists(lock_filepath): - return - return os.remove(lock_filepath) + if is_workfile_locked_for_current_process(filepath): + delete_workfile_lock(filepath) + + +def _get_process_id(): + process_id = os.environ.get("OPENPYPE_PROCESS_ID") + if not process_id: + process_id = str(uuid4()) + os.environ["OPENPYPE_PROCESS_ID"] = process_id + return process_id + +def is_workfile_lock_enabled(host_name, project_name, project_setting=None): + if project_setting is None: + project_setting = get_project_settings(project_name) + workfile_lock_profiles = ( + project_setting + ["global"] + ["tools"] + ["Workfiles"] + ["workfile_lock_profiles"]) + profile = filter_profiles(workfile_lock_profiles,{"host_name": host_name}) + if not profile: + return False + return profile["enabled"] diff --git a/openpype/settings/defaults/project_settings/global.json b/openpype/settings/defaults/project_settings/global.json index 0ff9363ba7..fc98a06ef1 100644 --- a/openpype/settings/defaults/project_settings/global.json +++ b/openpype/settings/defaults/project_settings/global.json @@ -403,7 +403,8 @@ "enabled": false } ], - "extra_folders": [] + "extra_folders": [], + "workfile_lock_profiles": [] }, "loader": { "family_filter_profiles": [ diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json index f8c9482e5f..d422278667 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_global_tools.json @@ -238,6 +238,31 @@ } ] } + }, + { + "type": "list", + "key": "workfile_lock_profiles", + "label": "Workfile lock profiles", + "use_label_wrap": true, + "object_type": { + "type": "dict", + "children": [ + { + "type": "hosts-enum", + "key": "host_name", + "label": "Hosts", + "multiselection": true + }, + { + "type": "splitter" + }, + { + "key": "enabled", + "label": "Enabled", + "type": "boolean" + } + ] + } } ] }, diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 6a554efd8b..5eab3af144 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -10,7 +10,9 @@ from openpype.host import IWorkfileHost from openpype.client import get_asset_by_id from openpype.pipeline.workfile.lock_workfile import ( is_workfile_locked, - get_username + get_user_from_lock, + is_workfile_lock_enabled, + is_workfile_locked_for_current_process ) from openpype.tools.utils import PlaceholderLineEdit from openpype.tools.utils.delegates import PrettyTimeDelegate @@ -456,10 +458,17 @@ class FilesWidget(QtWidgets.QWidget): "host_name": self.host_name } + def _is_workfile_locked(self, filepath): + if not is_workfile_lock_enabled(self.host_name, self.project_name): + return False + if not is_workfile_locked(filepath): + return False + return not is_workfile_locked_for_current_process(filepath) + def open_file(self, filepath): host = self.host - if is_workfile_locked(filepath): - username = get_username(filepath) + if self._is_workfile_locked(filepath): + username = get_user_from_lock(filepath) popup_dialog = QtWidgets.QMessageBox(parent=self) popup_dialog.setWindowTitle("Warning") popup_dialog.setText(username + " is using the file") From 4978981631933e98aba32fd8ebed363382ac5a06 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Sep 2022 13:25:12 +0800 Subject: [PATCH 050/144] adding lock task workfiles when users are working on them --- openpype/hosts/maya/api/pipeline.py | 2 +- openpype/pipeline/workfile/lock_workfile.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 67cf80e707..355537b92c 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -505,7 +505,7 @@ def check_lock_on_current_file(): columnSpacing=[(3, 10)]) cmds.separator(vis=False) cancel_command = "cmds.file(new=True);cmds.deleteUI('%s')" % reminder - ignore_command ="cmds.deleteUI('%s')" % reminder + ignore_command = "cmds.deleteUI('%s')" % reminder cmds.button(label='Cancel', command=cancel_command) cmds.button(label = "Ignore", command=ignore_command) cmds.showWindow(reminder) diff --git a/openpype/pipeline/workfile/lock_workfile.py b/openpype/pipeline/workfile/lock_workfile.py index b62f80c507..2a7f25e524 100644 --- a/openpype/pipeline/workfile/lock_workfile.py +++ b/openpype/pipeline/workfile/lock_workfile.py @@ -70,6 +70,7 @@ def _get_process_id(): os.environ["OPENPYPE_PROCESS_ID"] = process_id return process_id + def is_workfile_lock_enabled(host_name, project_name, project_setting=None): if project_setting is None: project_setting = get_project_settings(project_name) From 365a90c3c17c0d65bb6f97b9fd25d8299e3b0c0b Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 9 Sep 2022 13:26:58 +0800 Subject: [PATCH 051/144] adding lock task workfiles when users are working on them --- openpype/hosts/maya/api/pipeline.py | 4 ++-- openpype/pipeline/workfile/lock_workfile.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 355537b92c..b34a216c13 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -501,13 +501,13 @@ def check_lock_on_current_file(): cmds.text(comment, align='center') cmds.text(vis=False) cmds.rowColumnLayout(numberOfColumns=3, - columnWidth=[(1,200), (2, 100), (3, 100)], + columnWidth=[(1, 200), (2, 100), (3, 100)], columnSpacing=[(3, 10)]) cmds.separator(vis=False) cancel_command = "cmds.file(new=True);cmds.deleteUI('%s')" % reminder ignore_command = "cmds.deleteUI('%s')" % reminder cmds.button(label='Cancel', command=cancel_command) - cmds.button(label = "Ignore", command=ignore_command) + cmds.button(label="Ignore", command=ignore_command) cmds.showWindow(reminder) diff --git a/openpype/pipeline/workfile/lock_workfile.py b/openpype/pipeline/workfile/lock_workfile.py index 2a7f25e524..7c8c4a8066 100644 --- a/openpype/pipeline/workfile/lock_workfile.py +++ b/openpype/pipeline/workfile/lock_workfile.py @@ -80,7 +80,7 @@ def is_workfile_lock_enabled(host_name, project_name, project_setting=None): ["tools"] ["Workfiles"] ["workfile_lock_profiles"]) - profile = filter_profiles(workfile_lock_profiles,{"host_name": host_name}) + profile = filter_profiles(workfile_lock_profiles, {"host_name": host_name}) if not profile: return False return profile["enabled"] From 872d85f91d31f678c28759c05dd35ed388bab8ec Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Sep 2022 11:52:33 +0200 Subject: [PATCH 052/144] Remove old legacy plug-ins that are of no use anymore --- .../plugins/publish/collect_maya_scene.py | 25 ------------------- .../hosts/maya/plugins/publish/collect_rig.py | 22 ---------------- 2 files changed, 47 deletions(-) delete mode 100644 openpype/hosts/maya/plugins/publish/collect_maya_scene.py delete mode 100644 openpype/hosts/maya/plugins/publish/collect_rig.py diff --git a/openpype/hosts/maya/plugins/publish/collect_maya_scene.py b/openpype/hosts/maya/plugins/publish/collect_maya_scene.py deleted file mode 100644 index eb21b17989..0000000000 --- a/openpype/hosts/maya/plugins/publish/collect_maya_scene.py +++ /dev/null @@ -1,25 +0,0 @@ -from maya import cmds - -import pyblish.api - - -class CollectMayaScene(pyblish.api.InstancePlugin): - """Collect Maya Scene Data - - """ - - order = pyblish.api.CollectorOrder + 0.2 - label = 'Collect Model Data' - families = ["mayaScene"] - - def process(self, instance): - # Extract only current frame (override) - frame = cmds.currentTime(query=True) - instance.data["frameStart"] = frame - instance.data["frameEnd"] = frame - - # make ftrack publishable - if instance.data.get('families'): - instance.data['families'].append('ftrack') - else: - instance.data['families'] = ['ftrack'] diff --git a/openpype/hosts/maya/plugins/publish/collect_rig.py b/openpype/hosts/maya/plugins/publish/collect_rig.py deleted file mode 100644 index 98ae1e8009..0000000000 --- a/openpype/hosts/maya/plugins/publish/collect_rig.py +++ /dev/null @@ -1,22 +0,0 @@ -from maya import cmds - -import pyblish.api - - -class CollectRigData(pyblish.api.InstancePlugin): - """Collect rig data - - Ensures rigs are published to Ftrack. - - """ - - order = pyblish.api.CollectorOrder + 0.2 - label = 'Collect Rig Data' - families = ["rig"] - - def process(self, instance): - # make ftrack publishable - if instance.data.get('families'): - instance.data['families'].append('ftrack') - else: - instance.data['families'] = ['ftrack'] From ad9e172e4a0d97be2124aa6816ba18a28008e8cb Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Sep 2022 12:02:10 +0200 Subject: [PATCH 053/144] Add functionality back in to store playback time range with the mayaScene publish --- .../publish/collect_maya_scene_time.py | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py diff --git a/openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py b/openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py new file mode 100644 index 0000000000..71ca785e1d --- /dev/null +++ b/openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py @@ -0,0 +1,26 @@ +from maya import cmds + +import pyblish.api + + +class CollectMayaSceneTime(pyblish.api.InstancePlugin): + """Collect Maya Scene playback range + + This allows to reproduce the playback range for the content to be loaded. + It does *not* limit the extracted data to only data inside that time range. + + """ + + order = pyblish.api.CollectorOrder + 0.2 + label = 'Collect Maya Scene Time' + families = ["mayaScene"] + + def process(self, instance): + instance.data.update({ + "frameStart": cmds.playbackOptions(query=True, minTime=True), + "frameEnd": cmds.playbackOptions(query=True, maxTime=True), + "frameStartHandle": cmds.playbackOptions(query=True, + animationStartTime=True), + "frameEndHandle": cmds.playbackOptions(query=True, + animationEndTime=True), + }) From b7ee7669af0a3f43f1050d24818c2c7550331ba6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Sep 2022 12:03:32 +0200 Subject: [PATCH 054/144] Remove redundant comma --- openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py b/openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py index 71ca785e1d..7e198df14d 100644 --- a/openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py +++ b/openpype/hosts/maya/plugins/publish/collect_maya_scene_time.py @@ -22,5 +22,5 @@ class CollectMayaSceneTime(pyblish.api.InstancePlugin): "frameStartHandle": cmds.playbackOptions(query=True, animationStartTime=True), "frameEndHandle": cmds.playbackOptions(query=True, - animationEndTime=True), + animationEndTime=True) }) From d0665c3928ecff4176f6e22b676384f09485c173 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Fri, 9 Sep 2022 13:06:12 +0200 Subject: [PATCH 055/144] Change `mayaascii` -> `mayaAscii` --- .../ftrack/plugins/publish/integrate_ftrack_instances.py | 2 +- openpype/settings/defaults/project_settings/ftrack.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 6024781d87..7e5815b100 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -35,7 +35,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): family_mapping = { "camera": "cam", "look": "look", - "mayaascii": "scene", + "mayaAscii": "scene", "model": "geo", "rig": "rig", "setdress": "setdress", diff --git a/openpype/settings/defaults/project_settings/ftrack.json b/openpype/settings/defaults/project_settings/ftrack.json index 09b194e21c..cdf861df4a 100644 --- a/openpype/settings/defaults/project_settings/ftrack.json +++ b/openpype/settings/defaults/project_settings/ftrack.json @@ -455,7 +455,7 @@ "family_mapping": { "camera": "cam", "look": "look", - "mayaascii": "scene", + "mayaAscii": "scene", "model": "geo", "rig": "rig", "setdress": "setdress", From 4ba3ff21ab10e5b0c092f9580dc6444bdd61383c Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 12 Sep 2022 11:18:09 +0200 Subject: [PATCH 056/144] Tweak back more to intended logic --- .../plugins/publish/submit_maya_deadline.py | 29 ++++++++++++------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index f8d0af9752..45790c40ea 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -754,7 +754,12 @@ def _format_tiles( used for assembler configuration. """ - tile = 0 + # Math used requires integers for correct output - as such + # we ensure our inputs are correct. + assert type(tiles_x) is int, "tiles_x must be an integer" + assert type(tiles_y) is int, "tiles_y must be an integer" + assert type(width) is int, "width must be an integer" + assert type(height) is int, "height must be an integer" out = {"JobInfo": {}, "PluginInfo": {}} cfg = OrderedDict() w_space = width // tiles_x @@ -762,6 +767,7 @@ def _format_tiles( cfg["TilesCropped"] = "False" + tile = 0 for tile_x in range(1, tiles_x + 1): for tile_y in reversed(range(1, tiles_y + 1)): tile_prefix = "_tile_{}x{}_{}x{}_".format( @@ -769,28 +775,31 @@ def _format_tiles( tiles_x, tiles_y ) - out_tile_index = "OutputFilename{}Tile{}".format( - str(index), tile - ) + new_filename = "{}/{}{}".format( os.path.dirname(filename), tile_prefix, os.path.basename(filename) ) - out["JobInfo"][out_tile_index] = new_filename - out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = \ - "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) - top = int(height) - (tile_y * h_space) - bottom = int(height) - ((tile_y - 1) * h_space) - 1 + top = height - (tile_y * h_space) + bottom = height - ((tile_y - 1) * h_space) - 1 left = (tile_x - 1) * w_space right = (tile_x * w_space) - 1 + # Job info + out["JobInfo"]["OutputFilename{}Tile{}".format(index, tile)] = new_filename # noqa: E501 + + # Plugin Info + out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = \ + "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) + out["PluginInfo"]["RegionTop{}".format(tile)] = top out["PluginInfo"]["RegionBottom{}".format(tile)] = bottom out["PluginInfo"]["RegionLeft{}".format(tile)] = left out["PluginInfo"]["RegionRight{}".format(tile)] = right + # Tile config cfg["Tile{}".format(tile)] = new_filename cfg["Tile{}Tile".format(tile)] = new_filename cfg["Tile{}FileName".format(tile)] = new_filename @@ -801,5 +810,5 @@ def _format_tiles( cfg["Tile{}Height".format(tile)] = h_space tile += 1 - + return out, cfg From 41a738bd12efc48aec512a83ee36ffd9b4ddcb3a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Mon, 12 Sep 2022 11:19:28 +0200 Subject: [PATCH 057/144] Cosmetics --- .../modules/deadline/plugins/publish/submit_maya_deadline.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py index 45790c40ea..44f2b5b2b4 100644 --- a/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py +++ b/openpype/modules/deadline/plugins/publish/submit_maya_deadline.py @@ -760,6 +760,7 @@ def _format_tiles( assert type(tiles_y) is int, "tiles_y must be an integer" assert type(width) is int, "width must be an integer" assert type(height) is int, "height must be an integer" + out = {"JobInfo": {}, "PluginInfo": {}} cfg = OrderedDict() w_space = width // tiles_x @@ -793,7 +794,6 @@ def _format_tiles( # Plugin Info out["PluginInfo"]["RegionPrefix{}".format(str(tile))] = \ "/{}".format(tile_prefix).join(prefix.rsplit("/", 1)) - out["PluginInfo"]["RegionTop{}".format(tile)] = top out["PluginInfo"]["RegionBottom{}".format(tile)] = bottom out["PluginInfo"]["RegionLeft{}".format(tile)] = left @@ -805,7 +805,6 @@ def _format_tiles( cfg["Tile{}FileName".format(tile)] = new_filename cfg["Tile{}X".format(tile)] = left cfg["Tile{}Y".format(tile)] = top - cfg["Tile{}Width".format(tile)] = w_space cfg["Tile{}Height".format(tile)] = h_space From 6e2ffc1e5ceb134f17fcedd1646f2cec0014a43a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 10:33:41 +0200 Subject: [PATCH 058/144] Remove getting project name and settings twice --- openpype/hosts/maya/api/pipeline.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index acd8a55aa4..45c52cd0d5 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -66,8 +66,6 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): project_name = legacy_io.active_project() project_settings = get_project_settings(project_name) # process path mapping - project_name = legacy_io.active_project() - project_settings = get_project_settings(project_name) dirmap_processor = MayaDirmap("maya", project_name, project_settings) dirmap_processor.process_dirmap() From f6792d2e420fc75d5a3c4aee489c40a48be56d6c Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 13 Sep 2022 18:04:45 +0800 Subject: [PATCH 059/144] adding a Qt lockfile dialog for lockfile tasks --- openpype/hosts/maya/api/pipeline.py | 27 ++++------ openpype/pipeline/workfile/lock_workfile.py | 14 ++---- openpype/tools/workfiles/files_widget.py | 14 ++---- openpype/tools/workfiles/lock_dialog.py | 55 +++++++++++++++++++++ 4 files changed, 74 insertions(+), 36 deletions(-) create mode 100644 openpype/tools/workfiles/lock_dialog.py diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index b34a216c13..5c7a7abf4d 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -480,6 +480,7 @@ def on_before_save(): def check_lock_on_current_file(): + """Check if there is a user opening the file""" if not handle_workfile_locks(): return @@ -492,23 +493,15 @@ def check_lock_on_current_file(): create_workfile_lock(filepath) return - username = get_user_from_lock(filepath) - reminder = cmds.window(title="Reminder", width=400, height=30) - cmds.columnLayout(adjustableColumn=True) - cmds.separator() - cmds.columnLayout(adjustableColumn=True) - comment = " %s is working the same workfile!" % username - cmds.text(comment, align='center') - cmds.text(vis=False) - cmds.rowColumnLayout(numberOfColumns=3, - columnWidth=[(1, 200), (2, 100), (3, 100)], - columnSpacing=[(3, 10)]) - cmds.separator(vis=False) - cancel_command = "cmds.file(new=True);cmds.deleteUI('%s')" % reminder - ignore_command = "cmds.deleteUI('%s')" % reminder - cmds.button(label='Cancel', command=cancel_command) - cmds.button(label="Ignore", command=ignore_command) - cmds.showWindow(reminder) + # add lockfile dialog + from Qt import QtWidgets + from openpype.tools.workfiles.lock_dialog import WorkfileLockDialog + + top_level_widgets = {w.objectName(): w for w in + QtWidgets.QApplication.topLevelWidgets()} + parent = top_level_widgets.get("MayaWindow", None) + workfile_dialog = WorkfileLockDialog(filepath, parent=parent) + workfile_dialog.show() def on_before_close(): diff --git a/openpype/pipeline/workfile/lock_workfile.py b/openpype/pipeline/workfile/lock_workfile.py index 7c8c4a8066..fbec44247a 100644 --- a/openpype/pipeline/workfile/lock_workfile.py +++ b/openpype/pipeline/workfile/lock_workfile.py @@ -26,6 +26,11 @@ def is_workfile_locked(filepath): return True +def get_workfile_lock_data(filepath): + lock_filepath = _get_lock_file(filepath) + return _read_lock_file(lock_filepath) + + def is_workfile_locked_for_current_process(filepath): if not is_workfile_locked(filepath): return False @@ -49,15 +54,6 @@ def create_workfile_lock(filepath): json.dump(info, stream) -def get_user_from_lock(filepath): - lock_filepath = _get_lock_file(filepath) - if not os.path.exists(lock_filepath): - return - data = _read_lock_file(lock_filepath) - username = data["username"] - return username - - def remove_workfile_lock(filepath): if is_workfile_locked_for_current_process(filepath): delete_workfile_lock(filepath) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 5eab3af144..c1c647478d 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -10,7 +10,6 @@ from openpype.host import IWorkfileHost from openpype.client import get_asset_by_id from openpype.pipeline.workfile.lock_workfile import ( is_workfile_locked, - get_user_from_lock, is_workfile_lock_enabled, is_workfile_locked_for_current_process ) @@ -20,6 +19,7 @@ from openpype.lib import ( emit_event, create_workdir_extra_folders, ) +from openpype.tools.workfiles.lock_dialog import WorkfileLockDialog from openpype.pipeline import ( registered_host, legacy_io, @@ -30,6 +30,7 @@ from openpype.pipeline.context_tools import ( change_current_context ) from openpype.pipeline.workfile import get_workfile_template_key +from openpype.tools.workfiles.lock_dialog import WorkfileLockDialog from .model import ( WorkAreaFilesModel, @@ -468,15 +469,8 @@ class FilesWidget(QtWidgets.QWidget): def open_file(self, filepath): host = self.host if self._is_workfile_locked(filepath): - username = get_user_from_lock(filepath) - popup_dialog = QtWidgets.QMessageBox(parent=self) - popup_dialog.setWindowTitle("Warning") - popup_dialog.setText(username + " is using the file") - popup_dialog.setStandardButtons(popup_dialog.Ok) - - result = popup_dialog.exec_() - if result == popup_dialog.Ok: - return False + # add lockfile dialog + WorkfileLockDialog(filepath, parent=self) if isinstance(host, IWorkfileHost): has_unsaved_changes = host.workfile_has_unsaved_changes() diff --git a/openpype/tools/workfiles/lock_dialog.py b/openpype/tools/workfiles/lock_dialog.py new file mode 100644 index 0000000000..6c0ad6e850 --- /dev/null +++ b/openpype/tools/workfiles/lock_dialog.py @@ -0,0 +1,55 @@ +from Qt import QtWidgets, QtCore, QtGui +from openpype.style import load_stylesheet, get_app_icon_path + +from openpype.pipeline.workfile.lock_workfile import get_workfile_lock_data + + +class WorkfileLockDialog(QtWidgets.QDialog): + def __init__(self, workfile_path, parent=None): + super(WorkfileLockDialog, self).__init__(parent) + self.setWindowTitle("Warning") + icon = QtGui.QIcon(get_app_icon_path()) + self.setWindowIcon(icon) + + data = get_workfile_lock_data(workfile_path) + + message = "{} on {} machine is working on the same workfile.".format( + data["username"], + data["hostname"] + ) + + msg_label = QtWidgets.QLabel(message, self) + + btns_widget = QtWidgets.QWidget(self) + + cancel_btn = QtWidgets.QPushButton("Cancel", btns_widget) + ignore_btn = QtWidgets.QPushButton("Ignore lock", btns_widget) + + btns_layout = QtWidgets.QHBoxLayout(btns_widget) + btns_layout.setContentsMargins(0, 0, 0, 0) + btns_layout.setSpacing(10) + btns_layout.addStretch(1) + btns_layout.addWidget(cancel_btn, 0) + btns_layout.addWidget(ignore_btn, 0) + + main_layout = QtWidgets.QVBoxLayout(self) + main_layout.setContentsMargins(15, 15, 15, 15) + main_layout.addWidget(msg_label, 1, QtCore.Qt.AlignCenter), + main_layout.addSpacing(10) + main_layout.addWidget(btns_widget, 0) + + cancel_btn.clicked.connect(self._on_cancel_click) + ignore_btn.clicked.connect(self._on_ignore_click) + + def showEvent(self, event): + super(WorkfileLockDialog, self).showEvent(event) + + self.setStyleSheet(load_stylesheet()) + + def _on_ignore_click(self): + # Result is '1' + self.accept() + + def _on_cancel_click(self): + # Result is '0' + self.reject() From 3c949aaec3a9cffb6830a100e5b9dcec3f18b1aa Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Tue, 13 Sep 2022 18:06:35 +0800 Subject: [PATCH 060/144] adding a Qt lockfile dialog for lockfile tasks --- openpype/hosts/maya/api/pipeline.py | 1 - openpype/tools/workfiles/files_widget.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 5c7a7abf4d..87f34e1c05 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -33,7 +33,6 @@ from openpype.pipeline import ( from openpype.pipeline.load import any_outdated_containers from openpype.pipeline.workfile.lock_workfile import ( create_workfile_lock, - get_user_from_lock, remove_workfile_lock, is_workfile_locked, is_workfile_lock_enabled diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index c1c647478d..b59a7eccc5 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -30,7 +30,7 @@ from openpype.pipeline.context_tools import ( change_current_context ) from openpype.pipeline.workfile import get_workfile_template_key -from openpype.tools.workfiles.lock_dialog import WorkfileLockDialog + from .model import ( WorkAreaFilesModel, From b14ab9f2aff93fce85cec7b6403183a0b7dcf511 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 12:31:44 +0200 Subject: [PATCH 061/144] added publisher to host tools --- openpype/tools/utils/host_tools.py | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 52d15a59f7..3177ed35aa 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -32,6 +32,7 @@ class HostToolsHelper: self._workfiles_tool = None self._loader_tool = None self._creator_tool = None + self._publisher_tool = None self._subset_manager_tool = None self._scene_inventory_tool = None self._library_loader_tool = None @@ -205,6 +206,7 @@ class HostToolsHelper: pyblish_show = self._discover_pyblish_gui() return pyblish_show(parent) + def _discover_pyblish_gui(self): """Return the most desirable of the currently registered GUIs""" # Prefer last registered @@ -269,6 +271,30 @@ class HostToolsHelper: dialog.activateWindow() dialog.showNormal() + def get_publisher_tool(self, parent): + """Create, cache and return scene inventory tool window.""" + if self._scene_inventory_tool is None: + from openpype.tools.publisher import PublisherWindow + + host = registered_host() + ILoadHost.validate_load_methods(host) + + publisher_window = PublisherWindow( + parent=parent or self._parent + ) + self._publisher_tool = publisher_window + + return self._publisher_tool + + def show_publisher_tool(self, parent=None): + with qt_app_context(): + dialog = self.get_publisher_tool(parent) + + dialog.show() + dialog.raise_() + dialog.activateWindow() + dialog.showNormal() + def get_tool_by_name(self, tool_name, parent=None, *args, **kwargs): """Show tool by it's name. @@ -298,6 +324,10 @@ class HostToolsHelper: elif tool_name == "publish": self.log.info("Can't return publish tool window.") + # "new" publisher + elif tool_name == "publisher": + return self.get_publisher_tool(parent, *args, **kwargs) + elif tool_name == "experimental_tools": return self.get_experimental_tools_dialog(parent, *args, **kwargs) @@ -335,6 +365,9 @@ class HostToolsHelper: elif tool_name == "publish": self.show_publish(parent, *args, **kwargs) + elif tool_name == "publisher": + self.show_publisher_tool(parent, *args, **kwargs) + elif tool_name == "experimental_tools": self.show_experimental_tools_dialog(parent, *args, **kwargs) @@ -414,6 +447,10 @@ def show_publish(parent=None): _SingletonPoint.show_tool_by_name("publish", parent) +def show_publisher(parent=None): + _SingletonPoint.show_tool_by_name("publisher", parent) + + def show_experimental_tools_dialog(parent=None): _SingletonPoint.show_tool_by_name("experimental_tools", parent) From 7ad8aa34db533c97de270d520874a307caa93fe4 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 12:38:36 +0200 Subject: [PATCH 062/144] fix variable usage --- openpype/tools/utils/host_tools.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 3177ed35aa..f7e6d330ed 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -273,7 +273,8 @@ class HostToolsHelper: def get_publisher_tool(self, parent): """Create, cache and return scene inventory tool window.""" - if self._scene_inventory_tool is None: + + if self._publisher_tool is None: from openpype.tools.publisher import PublisherWindow host = registered_host() From a440a92838772c967f1bb844534153fe9814f4fc Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 13 Sep 2022 13:34:37 +0200 Subject: [PATCH 063/144] Fix docstring Co-authored-by: Roy Nieterau --- openpype/tools/utils/host_tools.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index f7e6d330ed..7208e0a500 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -272,7 +272,7 @@ class HostToolsHelper: dialog.showNormal() def get_publisher_tool(self, parent): - """Create, cache and return scene inventory tool window.""" + """Create, cache and return publisher window.""" if self._publisher_tool is None: from openpype.tools.publisher import PublisherWindow From 9e5e5d59210a82d6c171f3871834955d326b2a0b Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 13:35:38 +0200 Subject: [PATCH 064/144] remove unnecessary lines --- openpype/tools/utils/host_tools.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/tools/utils/host_tools.py b/openpype/tools/utils/host_tools.py index 7208e0a500..d2f05d3302 100644 --- a/openpype/tools/utils/host_tools.py +++ b/openpype/tools/utils/host_tools.py @@ -194,7 +194,6 @@ class HostToolsHelper: library_loader_tool.showNormal() library_loader_tool.refresh() - def show_publish(self, parent=None): """Try showing the most desirable publish GUI @@ -206,7 +205,6 @@ class HostToolsHelper: pyblish_show = self._discover_pyblish_gui() return pyblish_show(parent) - def _discover_pyblish_gui(self): """Return the most desirable of the currently registered GUIs""" # Prefer last registered From ecee2d2be5d33c4014effa836f620114cfc1bf9a Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 14:02:11 +0200 Subject: [PATCH 065/144] implemented 'check_ftrack_url' in ftrack module --- openpype/modules/ftrack/__init__.py | 8 ++- openpype/modules/ftrack/ftrack_module.py | 69 ++++++++++++++++++++---- 2 files changed, 64 insertions(+), 13 deletions(-) diff --git a/openpype/modules/ftrack/__init__.py b/openpype/modules/ftrack/__init__.py index 7261254c6f..6dc67b74b9 100644 --- a/openpype/modules/ftrack/__init__.py +++ b/openpype/modules/ftrack/__init__.py @@ -1,9 +1,13 @@ from .ftrack_module import ( FtrackModule, - FTRACK_MODULE_DIR + FTRACK_MODULE_DIR, + + check_ftrack_url, ) __all__ = ( "FtrackModule", - "FTRACK_MODULE_DIR" + "FTRACK_MODULE_DIR", + + "check_ftrack_url", ) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index cb4f204523..e00f9d89c6 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -12,8 +12,10 @@ from openpype_interfaces import ( ISettingsChangeListener ) from openpype.settings import SaveWarningExc +from openpype.lib import Logger FTRACK_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) +_PLACEHOLDER = object() class FtrackModule( @@ -28,17 +30,8 @@ class FtrackModule( ftrack_settings = settings[self.name] self.enabled = ftrack_settings["enabled"] - # Add http schema - ftrack_url = ftrack_settings["ftrack_server"].strip("/ ") - if ftrack_url: - if "http" not in ftrack_url: - ftrack_url = "https://" + ftrack_url - - # Check if "ftrack.app" is part os url - if "ftrackapp.com" not in ftrack_url: - ftrack_url = ftrack_url + ".ftrackapp.com" - - self.ftrack_url = ftrack_url + self._settings_ftrack_url = ftrack_settings["ftrack_server"] + self._ftrack_url = _PLACEHOLDER current_dir = os.path.dirname(os.path.abspath(__file__)) low_platform = platform.system().lower() @@ -70,6 +63,16 @@ class FtrackModule( self.timers_manager_connector = None self._timers_manager_module = None + def get_ftrack_url(self): + if self._ftrack_url is _PLACEHOLDER: + self._ftrack_url = check_ftrack_url( + self._settings_ftrack_url, + logger=self.log + ) + return self._ftrack_url + + ftrack_url = property(get_ftrack_url) + def get_global_environments(self): """Ftrack's global environments.""" return { @@ -479,6 +482,50 @@ class FtrackModule( click_group.add_command(cli_main) +def _check_ftrack_url(url): + import requests + + try: + result = requests.get(url, allow_redirects=False) + except requests.exceptions.RequestException: + return False + + if (result.status_code != 200 or "FTRACK_VERSION" not in result.headers): + return False + return True + + +def check_ftrack_url(url, log_errors=True, logger=None): + """Checks if Ftrack server is responding""" + + if logger is None: + logger = Logger.get_logger(__name__) + + url = url.strip("/ ") + if not url: + logger.error("Ftrack URL is not set!") + return None + + if not url.startswith("http"): + url = "https://" + url + + ftrack_url = None + if not url.endswith("ftrackapp.com"): + ftrackapp_url = url + ".ftrackapp.com" + if _check_ftrack_url(ftrackapp_url): + ftrack_url = ftrackapp_url + + if not ftrack_url and _check_ftrack_url(url): + ftrack_url = url + + if ftrack_url: + logger.debug("Ftrack server \"{}\" is accessible.".format(ftrack_url)) + elif log_errors: + logger.error("Entered Ftrack URL \"{}\" is not accesible!".format(url)) + + return ftrack_url + + @click.group(FtrackModule.name, help="Ftrack module related commands.") def cli_main(): pass From b29f26b28cb9350c0460b8bd8b89a8bfcbf0c7cd Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 14:02:42 +0200 Subject: [PATCH 066/144] changed imports in ftrack tray --- openpype/modules/ftrack/tray/ftrack_tray.py | 40 +++++++++------------ 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index 501d837a4c..a6a87b8ef9 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -6,22 +6,18 @@ import threading from Qt import QtCore, QtWidgets, QtGui import ftrack_api -from ..ftrack_server.lib import check_ftrack_url -from ..ftrack_server import socket_thread -from ..lib import credentials -from ..ftrack_module import FTRACK_MODULE_DIR -from . import login_dialog - from openpype import resources from openpype.lib import Logger - - -log = Logger.get_logger("FtrackModule") +from openpype_modules.ftrack import check_ftrack_url, FTRACK_MODULE_DIR +from openpype_modules.ftrack.ftrack_server import socket_thread +from openpype_modules.ftrack.lib import credentials +from . import login_dialog class FtrackTrayWrapper: def __init__(self, module): self.module = module + self.log = Logger.get_logger(self.__class__.__name__) self.thread_action_server = None self.thread_socket_server = None @@ -62,19 +58,19 @@ class FtrackTrayWrapper: if validation: self.widget_login.set_credentials(ft_user, ft_api_key) self.module.set_credentials_to_env(ft_user, ft_api_key) - log.info("Connected to Ftrack successfully") + self.log.info("Connected to Ftrack successfully") self.on_login_change() return validation if not validation and ft_user and ft_api_key: - log.warning( + self.log.warning( "Current Ftrack credentials are not valid. {}: {} - {}".format( str(os.environ.get("FTRACK_SERVER")), ft_user, ft_api_key ) ) - log.info("Please sign in to Ftrack") + self.log.info("Please sign in to Ftrack") self.bool_logged = False self.show_login_widget() self.set_menu_visibility() @@ -104,7 +100,7 @@ class FtrackTrayWrapper: self.action_credentials.setIcon(self.icon_not_logged) self.action_credentials.setToolTip("Logged out") - log.info("Logged out of Ftrack") + self.log.info("Logged out of Ftrack") self.bool_logged = False self.set_menu_visibility() @@ -126,10 +122,6 @@ class FtrackTrayWrapper: ftrack_url = self.module.ftrack_url os.environ["FTRACK_SERVER"] = ftrack_url - parent_file_path = os.path.dirname( - os.path.dirname(os.path.realpath(__file__)) - ) - min_fail_seconds = 5 max_fail_count = 3 wait_time_after_max_fail = 10 @@ -154,7 +146,7 @@ class FtrackTrayWrapper: # Main loop while True: if not self.bool_action_server_running: - log.debug("Action server was pushed to stop.") + self.log.debug("Action server was pushed to stop.") break # Check if accessible Ftrack and Mongo url @@ -164,7 +156,9 @@ class FtrackTrayWrapper: # Run threads only if Ftrack is accessible if not ftrack_accessible: if not printed_ftrack_error: - log.warning("Can't access Ftrack {}".format(ftrack_url)) + self.log.warning( + "Can't access Ftrack {}".format(ftrack_url) + ) if self.thread_socket_server is not None: self.thread_socket_server.stop() @@ -191,7 +185,7 @@ class FtrackTrayWrapper: self.set_menu_visibility() elif failed_count == max_fail_count: - log.warning(( + self.log.warning(( "Action server failed {} times." " I'll try to run again {}s later" ).format( @@ -243,10 +237,10 @@ class FtrackTrayWrapper: self.thread_action_server.join() self.thread_action_server = None - log.info("Ftrack action server was forced to stop") + self.log.info("Ftrack action server was forced to stop") except Exception: - log.warning( + self.log.warning( "Error has happened during Killing action server", exc_info=True ) @@ -343,7 +337,7 @@ class FtrackTrayWrapper: self.thread_timer = None except Exception as e: - log.error("During Killing Timer event server: {0}".format(e)) + self.log.error("During Killing Timer event server: {0}".format(e)) def changed_user(self): self.stop_action_server() From 21e050a8f18a272da3e200405550be6570e9f3d9 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 14:05:25 +0200 Subject: [PATCH 067/144] use new import of 'check_ftrack_url' --- openpype/modules/ftrack/ftrack_module.py | 2 +- .../modules/ftrack/ftrack_server/__init__.py | 2 -- .../ftrack/ftrack_server/event_server_cli.py | 6 ++-- openpype/modules/ftrack/ftrack_server/lib.py | 35 +------------------ openpype/modules/ftrack/lib/avalon_sync.py | 7 ++-- 5 files changed, 8 insertions(+), 44 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index e00f9d89c6..899711e33e 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -6,7 +6,7 @@ import platform import click from openpype.modules import OpenPypeModule -from openpype_interfaces import ( +from openpype.modules.interfaces import ( ITrayModule, IPluginPaths, ISettingsChangeListener diff --git a/openpype/modules/ftrack/ftrack_server/__init__.py b/openpype/modules/ftrack/ftrack_server/__init__.py index 9e3920b500..8e5f7c4c51 100644 --- a/openpype/modules/ftrack/ftrack_server/__init__.py +++ b/openpype/modules/ftrack/ftrack_server/__init__.py @@ -1,8 +1,6 @@ from .ftrack_server import FtrackServer -from .lib import check_ftrack_url __all__ = ( "FtrackServer", - "check_ftrack_url" ) diff --git a/openpype/modules/ftrack/ftrack_server/event_server_cli.py b/openpype/modules/ftrack/ftrack_server/event_server_cli.py index 3ef7c8270a..2848469bc3 100644 --- a/openpype/modules/ftrack/ftrack_server/event_server_cli.py +++ b/openpype/modules/ftrack/ftrack_server/event_server_cli.py @@ -20,9 +20,11 @@ from openpype.lib import ( get_openpype_version, get_build_version, ) -from openpype_modules.ftrack import FTRACK_MODULE_DIR +from openpype_modules.ftrack import ( + FTRACK_MODULE_DIR, + check_ftrack_url, +) from openpype_modules.ftrack.lib import credentials -from openpype_modules.ftrack.ftrack_server.lib import check_ftrack_url from openpype_modules.ftrack.ftrack_server import socket_thread diff --git a/openpype/modules/ftrack/ftrack_server/lib.py b/openpype/modules/ftrack/ftrack_server/lib.py index 947dacf917..c8143f739c 100644 --- a/openpype/modules/ftrack/ftrack_server/lib.py +++ b/openpype/modules/ftrack/ftrack_server/lib.py @@ -26,45 +26,12 @@ except ImportError: from openpype_modules.ftrack.lib import get_ftrack_event_mongo_info from openpype.client import OpenPypeMongoConnection -from openpype.api import Logger +from openpype.lib import Logger TOPIC_STATUS_SERVER = "openpype.event.server.status" TOPIC_STATUS_SERVER_RESULT = "openpype.event.server.status.result" -def check_ftrack_url(url, log_errors=True, logger=None): - """Checks if Ftrack server is responding""" - if logger is None: - logger = Logger.get_logger(__name__) - - if not url: - logger.error("Ftrack URL is not set!") - return None - - url = url.strip('/ ') - - if 'http' not in url: - if url.endswith('ftrackapp.com'): - url = 'https://' + url - else: - url = 'https://{0}.ftrackapp.com'.format(url) - try: - result = requests.get(url, allow_redirects=False) - except requests.exceptions.RequestException: - if log_errors: - logger.error("Entered Ftrack URL is not accesible!") - return False - - if (result.status_code != 200 or 'FTRACK_VERSION' not in result.headers): - if log_errors: - logger.error("Entered Ftrack URL is not accesible!") - return False - - logger.debug("Ftrack server {} is accessible.".format(url)) - - return url - - class SocketBaseEventHub(ftrack_api.event.hub.EventHub): hearbeat_msg = b"hearbeat" diff --git a/openpype/modules/ftrack/lib/avalon_sync.py b/openpype/modules/ftrack/lib/avalon_sync.py index 72be6a8e9a..935d1e85c9 100644 --- a/openpype/modules/ftrack/lib/avalon_sync.py +++ b/openpype/modules/ftrack/lib/avalon_sync.py @@ -19,11 +19,8 @@ from openpype.client.operations import ( CURRENT_PROJECT_SCHEMA, CURRENT_PROJECT_CONFIG_SCHEMA, ) -from openpype.api import ( - Logger, - get_anatomy_settings -) -from openpype.lib import ApplicationManager +from openpype.settings import get_anatomy_settings +from openpype.lib import ApplicationManager, Logger from openpype.pipeline import AvalonMongoDB, schema from .constants import CUST_ATTR_ID_KEY, FPS_KEYS From 98f1312ce999e4be72a1a90bce99c75be967cdfd Mon Sep 17 00:00:00 2001 From: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> Date: Tue, 13 Sep 2022 15:06:31 +0200 Subject: [PATCH 068/144] Modify log message Co-authored-by: Roy Nieterau --- openpype/modules/ftrack/ftrack_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 899711e33e..2ab0eb8239 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -521,7 +521,7 @@ def check_ftrack_url(url, log_errors=True, logger=None): if ftrack_url: logger.debug("Ftrack server \"{}\" is accessible.".format(ftrack_url)) elif log_errors: - logger.error("Entered Ftrack URL \"{}\" is not accesible!".format(url)) + logger.error("Entered Ftrack URL \"{}\" is not accessible!".format(url)) return ftrack_url From aead601397e0ebecfafb6da62570c0585f627018 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 15:11:20 +0200 Subject: [PATCH 069/144] unify messages --- openpype/modules/ftrack/ftrack_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 2ab0eb8239..e79910372f 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -520,8 +520,9 @@ def check_ftrack_url(url, log_errors=True, logger=None): if ftrack_url: logger.debug("Ftrack server \"{}\" is accessible.".format(ftrack_url)) + elif log_errors: - logger.error("Entered Ftrack URL \"{}\" is not accessible!".format(url)) + logger.error("Ftrack server \"{}\" is not accessible!".format(url)) return ftrack_url From 0291d2a7054b6b551fc8e5dc1092a87a026838d8 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 15:15:18 +0200 Subject: [PATCH 070/144] renamed 'check_ftrack_url' to 'resolve_ftrack_url' --- openpype/modules/ftrack/__init__.py | 4 ++-- openpype/modules/ftrack/ftrack_module.py | 6 +++--- openpype/modules/ftrack/ftrack_server/event_server_cli.py | 8 ++++---- openpype/modules/ftrack/tray/ftrack_tray.py | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/openpype/modules/ftrack/__init__.py b/openpype/modules/ftrack/__init__.py index 6dc67b74b9..e520f08337 100644 --- a/openpype/modules/ftrack/__init__.py +++ b/openpype/modules/ftrack/__init__.py @@ -2,12 +2,12 @@ from .ftrack_module import ( FtrackModule, FTRACK_MODULE_DIR, - check_ftrack_url, + resolve_ftrack_url, ) __all__ = ( "FtrackModule", "FTRACK_MODULE_DIR", - "check_ftrack_url", + "resolve_ftrack_url", ) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index e79910372f..05ea7b79d1 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -65,7 +65,7 @@ class FtrackModule( def get_ftrack_url(self): if self._ftrack_url is _PLACEHOLDER: - self._ftrack_url = check_ftrack_url( + self._ftrack_url = resolve_ftrack_url( self._settings_ftrack_url, logger=self.log ) @@ -495,8 +495,8 @@ def _check_ftrack_url(url): return True -def check_ftrack_url(url, log_errors=True, logger=None): - """Checks if Ftrack server is responding""" +def resolve_ftrack_url(url, log_errors=True, logger=None): + """Checks if Ftrack server is responding.""" if logger is None: logger = Logger.get_logger(__name__) diff --git a/openpype/modules/ftrack/ftrack_server/event_server_cli.py b/openpype/modules/ftrack/ftrack_server/event_server_cli.py index 2848469bc3..20c5ab24a8 100644 --- a/openpype/modules/ftrack/ftrack_server/event_server_cli.py +++ b/openpype/modules/ftrack/ftrack_server/event_server_cli.py @@ -22,7 +22,7 @@ from openpype.lib import ( ) from openpype_modules.ftrack import ( FTRACK_MODULE_DIR, - check_ftrack_url, + resolve_ftrack_url, ) from openpype_modules.ftrack.lib import credentials from openpype_modules.ftrack.ftrack_server import socket_thread @@ -116,7 +116,7 @@ def legacy_server(ftrack_url): while True: if not ftrack_accessible: - ftrack_accessible = check_ftrack_url(ftrack_url) + ftrack_accessible = resolve_ftrack_url(ftrack_url) # Run threads only if Ftrack is accessible if not ftrack_accessible and not printed_ftrack_error: @@ -259,7 +259,7 @@ def main_loop(ftrack_url): while True: # Check if accessible Ftrack and Mongo url if not ftrack_accessible: - ftrack_accessible = check_ftrack_url(ftrack_url) + ftrack_accessible = resolve_ftrack_url(ftrack_url) if not mongo_accessible: mongo_accessible = check_mongo_url(mongo_uri) @@ -443,7 +443,7 @@ def run_event_server( os.environ["CLOCKIFY_API_KEY"] = clockify_api_key # Check url regex and accessibility - ftrack_url = check_ftrack_url(ftrack_url) + ftrack_url = resolve_ftrack_url(ftrack_url) if not ftrack_url: print('Exiting! < Please enter Ftrack server url >') return 1 diff --git a/openpype/modules/ftrack/tray/ftrack_tray.py b/openpype/modules/ftrack/tray/ftrack_tray.py index a6a87b8ef9..e3c6e30ead 100644 --- a/openpype/modules/ftrack/tray/ftrack_tray.py +++ b/openpype/modules/ftrack/tray/ftrack_tray.py @@ -8,7 +8,7 @@ from Qt import QtCore, QtWidgets, QtGui import ftrack_api from openpype import resources from openpype.lib import Logger -from openpype_modules.ftrack import check_ftrack_url, FTRACK_MODULE_DIR +from openpype_modules.ftrack import resolve_ftrack_url, FTRACK_MODULE_DIR from openpype_modules.ftrack.ftrack_server import socket_thread from openpype_modules.ftrack.lib import credentials from . import login_dialog @@ -151,7 +151,7 @@ class FtrackTrayWrapper: # Check if accessible Ftrack and Mongo url if not ftrack_accessible: - ftrack_accessible = check_ftrack_url(ftrack_url) + ftrack_accessible = resolve_ftrack_url(ftrack_url) # Run threads only if Ftrack is accessible if not ftrack_accessible: From 09519c25a804186f9cc4afb92131d0572211f712 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 15:17:25 +0200 Subject: [PATCH 071/144] removed unused argument 'log_errors' --- openpype/modules/ftrack/ftrack_module.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 05ea7b79d1..68575009b2 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -495,7 +495,7 @@ def _check_ftrack_url(url): return True -def resolve_ftrack_url(url, log_errors=True, logger=None): +def resolve_ftrack_url(url, logger=None): """Checks if Ftrack server is responding.""" if logger is None: @@ -521,7 +521,7 @@ def resolve_ftrack_url(url, log_errors=True, logger=None): if ftrack_url: logger.debug("Ftrack server \"{}\" is accessible.".format(ftrack_url)) - elif log_errors: + else: logger.error("Ftrack server \"{}\" is not accessible!".format(url)) return ftrack_url From 477266f1407e84e2ba9d086107e15e8fc5173e79 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Tue, 13 Sep 2022 15:18:56 +0200 Subject: [PATCH 072/144] better variable name for ftrack url value check --- openpype/modules/ftrack/ftrack_module.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/ftrack_module.py b/openpype/modules/ftrack/ftrack_module.py index 68575009b2..75ffd7f864 100644 --- a/openpype/modules/ftrack/ftrack_module.py +++ b/openpype/modules/ftrack/ftrack_module.py @@ -15,7 +15,7 @@ from openpype.settings import SaveWarningExc from openpype.lib import Logger FTRACK_MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) -_PLACEHOLDER = object() +_URL_NOT_SET = object() class FtrackModule( @@ -31,7 +31,7 @@ class FtrackModule( self.enabled = ftrack_settings["enabled"] self._settings_ftrack_url = ftrack_settings["ftrack_server"] - self._ftrack_url = _PLACEHOLDER + self._ftrack_url = _URL_NOT_SET current_dir = os.path.dirname(os.path.abspath(__file__)) low_platform = platform.system().lower() @@ -64,7 +64,7 @@ class FtrackModule( self._timers_manager_module = None def get_ftrack_url(self): - if self._ftrack_url is _PLACEHOLDER: + if self._ftrack_url is _URL_NOT_SET: self._ftrack_url = resolve_ftrack_url( self._settings_ftrack_url, logger=self.log From 778e0b2e491f5948f2932968a70f8f620204fb01 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 15:44:20 +0200 Subject: [PATCH 073/144] Perform case-insensitive lookup --- .../ftrack/plugins/publish/integrate_ftrack_instances.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 7e5815b100..5d39e12985 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -74,11 +74,14 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): version_number = int(instance_version) family = instance.data["family"] - family_low = family.lower() + # Perform case-insensitive family mapping + family_low = family.lower() asset_type = instance.data.get("ftrackFamily") - if not asset_type and family_low in self.family_mapping: - asset_type = self.family_mapping[family_low] + if not asset_type: + for map_family, map_value in self.family_mapping.items(): + if map_family.lower() == family_low: + asset_type = map_value if not asset_type: asset_type = "upload" From 4466d8a94249ad66546730b7135e34003f4aa4f8 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 15:45:11 +0200 Subject: [PATCH 074/144] Remove redundant logic since just above it's forced to be "upload" when `not asset_type` --- .../ftrack/plugins/publish/integrate_ftrack_instances.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index 5d39e12985..a35dbf71d4 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -89,15 +89,6 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): self.log.debug( "Family: {}\nMapping: {}".format(family_low, self.family_mapping) ) - - # Ignore this instance if neither "ftrackFamily" or a family mapping is - # found. - if not asset_type: - self.log.info(( - "Family \"{}\" does not match any asset type mapping" - ).format(family)) - return - status_name = self._get_asset_version_status_name(instance) # Base of component item data From 1bc37ace465f647b6af35a4a2b8cf2832bd94925 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 15:45:48 +0200 Subject: [PATCH 075/144] Actually break loop early on detected mapping --- .../modules/ftrack/plugins/publish/integrate_ftrack_instances.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py index a35dbf71d4..5ff75e7060 100644 --- a/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py +++ b/openpype/modules/ftrack/plugins/publish/integrate_ftrack_instances.py @@ -82,6 +82,7 @@ class IntegrateFtrackInstance(pyblish.api.InstancePlugin): for map_family, map_value in self.family_mapping.items(): if map_family.lower() == family_low: asset_type = map_value + break if not asset_type: asset_type = "upload" From b3bb5f8612ccb623f5da34e4270e8db9bcdb8a7e Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Tue, 13 Sep 2022 23:09:17 +0200 Subject: [PATCH 076/144] Always increment current file - similar to Maya --- .../plugins/publish/increment_current_file.py | 29 +++------------ .../increment_current_file_deadline.py | 35 ------------------- 2 files changed, 4 insertions(+), 60 deletions(-) delete mode 100644 openpype/hosts/houdini/plugins/publish/increment_current_file_deadline.py diff --git a/openpype/hosts/houdini/plugins/publish/increment_current_file.py b/openpype/hosts/houdini/plugins/publish/increment_current_file.py index 5cb14d732a..c990f481d3 100644 --- a/openpype/hosts/houdini/plugins/publish/increment_current_file.py +++ b/openpype/hosts/houdini/plugins/publish/increment_current_file.py @@ -2,10 +2,9 @@ import pyblish.api from openpype.lib import version_up from openpype.pipeline import registered_host -from openpype.pipeline.publish import get_errored_plugins_from_context -class IncrementCurrentFile(pyblish.api.InstancePlugin): +class IncrementCurrentFile(pyblish.api.ContextPlugin): """Increment the current file. Saves the current scene with an increased version number. @@ -15,30 +14,10 @@ class IncrementCurrentFile(pyblish.api.InstancePlugin): label = "Increment current file" order = pyblish.api.IntegratorOrder + 9.0 hosts = ["houdini"] - families = ["colorbleed.usdrender", "redshift_rop"] - targets = ["local"] + families = ["workfile"] + optional = True - def process(self, instance): - - # This should be a ContextPlugin, but this is a workaround - # for a bug in pyblish to run once for a family: issue #250 - context = instance.context - key = "__hasRun{}".format(self.__class__.__name__) - if context.data.get(key, False): - return - else: - context.data[key] = True - - context = instance.context - errored_plugins = get_errored_plugins_from_context(context) - if any( - plugin.__name__ == "HoudiniSubmitPublishDeadline" - for plugin in errored_plugins - ): - raise RuntimeError( - "Skipping incrementing current file because " - "submission to deadline failed." - ) + def process(self, context): # Filename must not have changed since collecting host = registered_host() diff --git a/openpype/hosts/houdini/plugins/publish/increment_current_file_deadline.py b/openpype/hosts/houdini/plugins/publish/increment_current_file_deadline.py deleted file mode 100644 index cb0d7e3680..0000000000 --- a/openpype/hosts/houdini/plugins/publish/increment_current_file_deadline.py +++ /dev/null @@ -1,35 +0,0 @@ -import pyblish.api - -import hou -from openpype.lib import version_up -from openpype.pipeline.publish import get_errored_plugins_from_context - - -class IncrementCurrentFileDeadline(pyblish.api.ContextPlugin): - """Increment the current file. - - Saves the current scene with an increased version number. - - """ - - label = "Increment current file" - order = pyblish.api.IntegratorOrder + 9.0 - hosts = ["houdini"] - targets = ["deadline"] - - def process(self, context): - - errored_plugins = get_errored_plugins_from_context(context) - if any( - plugin.__name__ == "HoudiniSubmitPublishDeadline" - for plugin in errored_plugins - ): - raise RuntimeError( - "Skipping incrementing current file because " - "submission to deadline failed." - ) - - current_filepath = context.data["currentFile"] - new_filepath = version_up(current_filepath) - - hou.hipFile.save(file_name=new_filepath, save_to_recent_files=True) From 48546ef24f6c9b1db77e6094792ba2b2f1f88bc3 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 14 Sep 2022 04:18:24 +0000 Subject: [PATCH 077/144] [Automated] Bump version --- CHANGELOG.md | 32 ++++++++++++++++++-------------- openpype/version.py | 2 +- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 46bf56f5bd..7d6b620d58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,22 @@ # Changelog +## [3.14.3-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) + +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.2...HEAD) + +**🚀 Enhancements** + +- Publisher: Add new publisher to host tools [\#3833](https://github.com/pypeclub/OpenPype/pull/3833) +- Maya: Workspace mel loaded from settings [\#3790](https://github.com/pypeclub/OpenPype/pull/3790) + +**🐛 Bug fixes** + +- Ftrack: Url validation does not require ftrackapp [\#3834](https://github.com/pypeclub/OpenPype/pull/3834) +- Maya+Ftrack: Change typo in family name `mayaascii` -\> `mayaAscii` [\#3820](https://github.com/pypeclub/OpenPype/pull/3820) + ## [3.14.2](https://github.com/pypeclub/OpenPype/tree/3.14.2) (2022-09-12) -[Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.1...3.14.2) +[Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.14.2-nightly.5...3.14.2) **🆕 New features** @@ -45,11 +59,10 @@ - Maya: Refactor submit deadline to use AbstractSubmitDeadline [\#3759](https://github.com/pypeclub/OpenPype/pull/3759) - General: Change publish template settings location [\#3755](https://github.com/pypeclub/OpenPype/pull/3755) - General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) -- Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) +- General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) - Fusion: Defined fusion as addon [\#3733](https://github.com/pypeclub/OpenPype/pull/3733) - Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) -- Blender: Define blender as module [\#3729](https://github.com/pypeclub/OpenPype/pull/3729) - Resolve: Define resolve as addon [\#3727](https://github.com/pypeclub/OpenPype/pull/3727) **Merged pull requests:** @@ -72,7 +85,6 @@ - General: Added helper getters to modules manager [\#3712](https://github.com/pypeclub/OpenPype/pull/3712) - Unreal: Define unreal as module and use host class [\#3701](https://github.com/pypeclub/OpenPype/pull/3701) - Settings: Lock settings UI session [\#3700](https://github.com/pypeclub/OpenPype/pull/3700) -- General: Benevolent context label collector [\#3686](https://github.com/pypeclub/OpenPype/pull/3686) **🐛 Bug fixes** @@ -86,17 +98,17 @@ - Settings: Fix project overrides save [\#3708](https://github.com/pypeclub/OpenPype/pull/3708) - Workfiles tool: Fix published workfile filtering [\#3704](https://github.com/pypeclub/OpenPype/pull/3704) - PS, AE: Provide default variant value for workfile subset [\#3703](https://github.com/pypeclub/OpenPype/pull/3703) -- Flame: retime is working on clip publishing [\#3684](https://github.com/pypeclub/OpenPype/pull/3684) **🔀 Refactored code** - General: Move delivery logic to pipeline [\#3751](https://github.com/pypeclub/OpenPype/pull/3751) -- General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - General: Host addons cleanup [\#3744](https://github.com/pypeclub/OpenPype/pull/3744) - Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) +- Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) - Harmony: Defined harmony as addon [\#3734](https://github.com/pypeclub/OpenPype/pull/3734) - General: Module interfaces cleanup [\#3731](https://github.com/pypeclub/OpenPype/pull/3731) - AfterEffects: Move AE functions from general lib [\#3730](https://github.com/pypeclub/OpenPype/pull/3730) +- Blender: Define blender as module [\#3729](https://github.com/pypeclub/OpenPype/pull/3729) - AfterEffects: Define AfterEffects as module [\#3728](https://github.com/pypeclub/OpenPype/pull/3728) - General: Replace PypeLogger with Logger [\#3725](https://github.com/pypeclub/OpenPype/pull/3725) - Nuke: Define nuke as module [\#3724](https://github.com/pypeclub/OpenPype/pull/3724) @@ -118,14 +130,6 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.14.0-nightly.1...3.14.0) -**🚀 Enhancements** - -- Ftrack: Addiotional component metadata [\#3685](https://github.com/pypeclub/OpenPype/pull/3685) - -**🐛 Bug fixes** - -- General: Switch from hero version to versioned works [\#3691](https://github.com/pypeclub/OpenPype/pull/3691) - ## [3.13.0](https://github.com/pypeclub/OpenPype/tree/3.13.0) (2022-08-09) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.13.0-nightly.1...3.13.0) diff --git a/openpype/version.py b/openpype/version.py index 8469b1712a..e8a65b04d2 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.2" +__version__ = "3.14.3-nightly.1" From d6b7e666e8fa5342c69a0ed027774c4bc3804e28 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 14 Sep 2022 12:37:30 +0800 Subject: [PATCH 078/144] adding a Qt lockfile dialog for lockfile tasks --- openpype/hosts/maya/api/pipeline.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 3b84d91158..35d0026357 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -493,7 +493,7 @@ def check_lock_on_current_file(): # add lockfile dialog from Qt import QtWidgets top_level_widgets = {w.objectName(): w for w in - QtWidgets.QApplication.topLevelWidgets()} + QtWidgets.QApplication.topLevelWidgets()} parent = top_level_widgets.get("MayaWindow", None) workfile_dialog = WorkfileLockDialog(filepath, parent=parent) if not workfile_dialog.exec_(): @@ -502,6 +502,7 @@ def check_lock_on_current_file(): create_workfile_lock(filepath) + def on_before_close(): """Delete the lock file after user quitting the Maya Scene""" log.info("Closing Maya...") From b1ebef457c23a3e48e077cd63040e7b15b9828a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Samohel?= Date: Wed, 14 Sep 2022 11:36:10 +0200 Subject: [PATCH 079/144] :sparkles: add script for python dependencies info --- tools/get_python_packages_info.py | 83 +++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 tools/get_python_packages_info.py diff --git a/tools/get_python_packages_info.py b/tools/get_python_packages_info.py new file mode 100644 index 0000000000..b4952840e6 --- /dev/null +++ b/tools/get_python_packages_info.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +"""Get version and license information on used Python packages. + +This is getting over all packages installed with Poetry and printing out +their name, version and available license information from PyPi in Markdown +table format. + +Usage: + ./.poetry/bin/poetry run python ./tools/get_python_packages_info.py + +""" + +import toml +import requests + + +packages = [] + +# define column headers +package_header = "Package" +version_header = "Version" +license_header = "License" + +name_col_width = len(package_header) +version_col_width = len(version_header) +license_col_width = len(license_header) + +# read lock file to get packages +with open("poetry.lock", "r") as fb: + lock_content = toml.load(fb) + + for package in lock_content["package"]: + # query pypi for license information + url = f"https://pypi.org/pypi/{package['name']}/json" + response = requests.get( + f"https://pypi.org/pypi/{package['name']}/json") + package_data = response.json() + version = package.get("version") or "N/A" + try: + package_license = package_data["info"].get("license") or "N/A" + except KeyError: + package_license = "N/A" + + if len(package_license) > 64: + package_license = f"{package_license[:32]}..." + packages.append( + ( + package["name"], + version, + package_license + ) + ) + + # update column width based on max string length + if len(package["name"]) > name_col_width: + name_col_width = len(package["name"]) + if len(version) > version_col_width: + version_col_width = len(version) + if len(package_license) > license_col_width: + license_col_width = len(package_license) + +# pad columns +name_col_width += 2 +version_col_width += 2 +license_col_width += 2 + +# print table header +print((f"|{package_header.center(name_col_width)}" + f"|{version_header.center(version_col_width)}" + f"|{license_header.center(license_col_width)}|")) + +print( + "|" + ("-" * len(package_header.center(name_col_width))) + + "|" + ("-" * len(version_header.center(version_col_width))) + + "|" + ("-" * len(license_header.center(license_col_width))) + "|") + +# print rest of the table +for package in packages: + print(( + f"|{package[0].center(name_col_width)}" + f"|{package[1].center(version_col_width)}" + f"|{package[2].center(license_col_width)}|" + )) From d5cb828edc611f9ed9f98b61ae5ae3591d797640 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 11:45:44 +0200 Subject: [PATCH 080/144] Move LoadedInSceneDelegate into Loader tool since it's specific to loader --- openpype/tools/loader/delegates.py | 28 ++++++++++++++++++++++++++++ openpype/tools/loader/widgets.py | 4 ++-- openpype/tools/utils/delegates.py | 27 --------------------------- 3 files changed, 30 insertions(+), 29 deletions(-) create mode 100644 openpype/tools/loader/delegates.py diff --git a/openpype/tools/loader/delegates.py b/openpype/tools/loader/delegates.py new file mode 100644 index 0000000000..e6663d48f1 --- /dev/null +++ b/openpype/tools/loader/delegates.py @@ -0,0 +1,28 @@ +from Qt import QtWidgets, QtGui, QtCore + + +class LoadedInSceneDelegate(QtWidgets.QStyledItemDelegate): + """Delegate for Loaded in Scene state columns. + + Shows "yes" or "no" for True or False values + Colorizes green or dark grey based on True or False values + + """ + + def __init__(self, *args, **kwargs): + super(LoadedInSceneDelegate, self).__init__(*args, **kwargs) + self._colors = { + True: QtGui.QColor(80, 170, 80), + False: QtGui.QColor(90, 90, 90) + } + + def displayText(self, value, locale): + return "yes" if value else "no" + + def initStyleOption(self, option, index): + super(LoadedInSceneDelegate, self).initStyleOption(option, index) + + # Colorize based on value + value = index.data(QtCore.Qt.DisplayRole) + color = self._colors[bool(value)] + option.palette.setBrush(QtGui.QPalette.Text, color) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 615f28f04d..98522c48ce 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -37,8 +37,7 @@ from openpype.tools.utils import ( ) from openpype.tools.utils.delegates import ( VersionDelegate, - PrettyTimeDelegate, - LoadedInSceneDelegate + PrettyTimeDelegate ) from openpype.tools.utils.widgets import ( OptionalMenu, @@ -59,6 +58,7 @@ from .model import ( ITEM_ID_ROLE ) from . import lib +from .delegates import LoadedInSceneDelegate from openpype.tools.utils.constants import ( LOCAL_PROVIDER_ROLE, diff --git a/openpype/tools/utils/delegates.py b/openpype/tools/utils/delegates.py index 3547251282..d6c2d69e76 100644 --- a/openpype/tools/utils/delegates.py +++ b/openpype/tools/utils/delegates.py @@ -291,30 +291,3 @@ class PrettyTimeDelegate(QtWidgets.QStyledItemDelegate): def displayText(self, value, locale): if value is not None: return pretty_timestamp(value) - - -class LoadedInSceneDelegate(QtWidgets.QStyledItemDelegate): - """Delegate for Loaded in Scene state columns. - - Shows "yes" or "no" for True or False values - Colorizes green or dark grey based on True or False values - - """ - - def __init__(self, *args, **kwargs): - super(LoadedInSceneDelegate, self).__init__(*args, **kwargs) - self._colors = { - True: QtGui.QColor(80, 170, 80), - False: QtGui.QColor(90, 90, 90) - } - - def displayText(self, value, locale): - return "yes" if value else "no" - - def initStyleOption(self, option, index): - super(LoadedInSceneDelegate, self).initStyleOption(option, index) - - # Colorize based on value - value = index.data(QtCore.Qt.DisplayRole) - color = self._colors[bool(value)] - option.palette.setBrush(QtGui.QPalette.Text, color) From d84b175efbc60c226609c6393dddedbf9960c7be Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 11:47:52 +0200 Subject: [PATCH 081/144] Support ILoadHost hosts Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/tools/loader/model.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 58d6787507..17af7bb2f7 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -569,6 +569,11 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): if self._host: time_since_refresh = time.time() - self._host_loaded_refresh_time if time_since_refresh > self._host_loaded_refresh_timeout: + if isinstance(self._host, ILoadHost): + containers = self._host.get_containers() + else: + containers = self._host.ls() + repre_ids = {con.get("representation") for con in self._host.ls()} self._loaded_representation_ids = repre_ids From a8909889c4662764042288245ef2d901d1218055 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 11:49:52 +0200 Subject: [PATCH 082/144] Fix refactored code --- openpype/tools/loader/model.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 17af7bb2f7..1c078325f3 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -25,6 +25,7 @@ from openpype.pipeline import ( from openpype.style import get_default_entity_icon_color from openpype.tools.utils.models import TreeModel, Item from openpype.tools.utils import lib +from openpype.host import ILoadHost from openpype.modules import ModulesManager from openpype.tools.utils.constants import ( @@ -573,9 +574,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): containers = self._host.get_containers() else: containers = self._host.ls() - - repre_ids = {con.get("representation") - for con in self._host.ls()} + + repre_ids = {con.get("representation") for con in containers} self._loaded_representation_ids = repre_ids self._host_loaded_refresh_time = time.time() From 1eae84fd49af6930eb7865451c17557bf1d4d8b6 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 11:58:10 +0200 Subject: [PATCH 083/144] Switch columns --- openpype/tools/loader/model.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/tools/loader/model.py b/openpype/tools/loader/model.py index 1c078325f3..77a8669c46 100644 --- a/openpype/tools/loader/model.py +++ b/openpype/tools/loader/model.py @@ -138,8 +138,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "duration", "handles", "step", - "repre_info", - "loaded_in_scene" + "loaded_in_scene", + "repre_info" ] column_labels_mapping = { @@ -153,8 +153,8 @@ class SubsetsModel(TreeModel, BaseRepresentationModel): "duration": "Duration", "handles": "Handles", "step": "Step", - "repre_info": "Availability", - "loaded_in_scene": "In scene" + "loaded_in_scene": "In scene", + "repre_info": "Availability" } SortAscendingRole = QtCore.Qt.UserRole + 2 From c3588e2f9d337109aea3c2630fb12079b617d81d Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 12:37:46 +0200 Subject: [PATCH 084/144] Make `loaded_in_scene` column 5 pixels wider + reorder to match with column order --- openpype/tools/loader/widgets.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/loader/widgets.py b/openpype/tools/loader/widgets.py index 98522c48ce..c028aa4174 100644 --- a/openpype/tools/loader/widgets.py +++ b/openpype/tools/loader/widgets.py @@ -170,8 +170,8 @@ class SubsetWidget(QtWidgets.QWidget): ("duration", 60), ("handles", 55), ("step", 10), - ("repre_info", 65), - ("loaded_in_scene", 20) + ("loaded_in_scene", 25), + ("repre_info", 65) ) def __init__( From 817886b234c00d6a9f2a9bd0902cf8a2a9cb9cb3 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 13:39:10 +0200 Subject: [PATCH 085/144] Fix typo in logic --- openpype/lib/transcoding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/lib/transcoding.py b/openpype/lib/transcoding.py index 60d5d3ed4a..51e34312f2 100644 --- a/openpype/lib/transcoding.py +++ b/openpype/lib/transcoding.py @@ -154,7 +154,7 @@ def convert_value_by_type_name(value_type, value, logger=None): elif parts_len == 4: divisor = 2 elif parts_len == 9: - divisor == 3 + divisor = 3 elif parts_len == 16: divisor = 4 else: From 5c3c4dcbb2acfe736cfc94e6360eb2eb52cd580b Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 13:43:43 +0200 Subject: [PATCH 086/144] Fix same typo in duplicated code --- .../plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py b/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py index 9fca1b5391..b259e18a94 100644 --- a/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py +++ b/openpype/modules/deadline/repository/custom/plugins/OpenPypeTileAssembler/OpenPypeTileAssembler.py @@ -71,7 +71,7 @@ def convert_value_by_type_name(value_type, value): elif parts_len == 4: divisor = 2 elif parts_len == 9: - divisor == 3 + divisor = 3 elif parts_len == 16: divisor = 4 else: From 1c6b23b674eec3ed1fc1b1e0a68931a5661b71a0 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 13:50:57 +0200 Subject: [PATCH 087/144] Fix `headsUpDisplay` key name Capture has a default setting named `headsUpDisplay` which is the long name for the setting `hud`. Thus when supplying `hud` as viewport option then `capture` will merge the key-values and thus will try to set both `headsUpDisplay` and `hud` value for the modelEditor which ends up ignoring `hud` and instead applying the `headsUpDisplay`. Thus, `hud` didn't do anything. --- openpype/settings/defaults/project_settings/maya.json | 2 +- .../schemas/projects_schema/schemas/schema_maya_capture.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 99ba4cdd5c..7759ac4e5e 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -731,7 +731,7 @@ "grid": false, "hairSystems": true, "handles": false, - "hud": false, + "headsUpDisplay": false, "hulls": false, "ikHandles": false, "imagePlane": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 7a40f349cc..ab35fd391f 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -441,8 +441,8 @@ }, { "type": "boolean", - "key": "hud", - "label": "hud" + "key": "headsUpDisplay", + "label": "headsUpDisplay" }, { "type": "boolean", From 9a19da923c783e253c2f249842ed5c1409d2a5c3 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 14 Sep 2022 20:49:48 +0800 Subject: [PATCH 088/144] adding a Qt lockfile dialog for lockfile tasks --- openpype/hosts/maya/api/pipeline.py | 41 ++++++++++++++++++------ openpype/tools/workfiles/files_widget.py | 4 +-- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 35d0026357..eb22eeeb3b 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -106,11 +106,13 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): register_event_callback("open", on_open) register_event_callback("new", on_new) register_event_callback("before.save", on_before_save) + register_event_callback("after.save", on_after_save) register_event_callback("before.close", on_before_close) register_event_callback("before.file.open", before_file_open) register_event_callback("taskChanged", on_task_changed) register_event_callback("workfile.open.before", before_workfile_open) register_event_callback("workfile.save.before", before_workfile_save) + register_event_callback("workfile.save.after", after_workfile_save) def open_workfile(self, filepath): return open_file(filepath) @@ -153,6 +155,10 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): OpenMaya.MSceneMessage.kBeforeSave, _on_scene_save ) + self._op_events[_after_scene_save] = OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kAfterSave, _after_scene_save + ) + self._op_events[_before_scene_save] = ( OpenMaya.MSceneMessage.addCheckCallback( OpenMaya.MSceneMessage.kBeforeSaveCheck, @@ -194,6 +200,7 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): self.log.info("Installed event handler _on_scene_save..") self.log.info("Installed event handler _before_scene_save..") + self.log.info("Insatall event handler _on_after_save..") self.log.info("Installed event handler _on_scene_new..") self.log.info("Installed event handler _on_maya_initialized..") self.log.info("Installed event handler _on_scene_open..") @@ -236,6 +243,8 @@ def _on_maya_initialized(*args): def _on_scene_new(*args): emit_event("new") +def _after_scene_save(*arg): + emit_event("after.save") def _on_scene_save(*args): emit_event("save") @@ -271,6 +280,7 @@ def _remove_workfile_lock(): if not handle_workfile_locks(): return filepath = current_file() + log.info("Removing lock on current file {}...".format(filepath)) if filepath: remove_workfile_lock(filepath) @@ -479,6 +489,13 @@ def on_before_save(): return lib.validate_fps() +def on_after_save(): + """Check if there is a lockfile after save""" + filepath = current_file() + if not is_workfile_locked(filepath): + create_workfile_lock(filepath) + + def check_lock_on_current_file(): """Check if there is a user opening the file""" @@ -491,14 +508,14 @@ def check_lock_on_current_file(): if is_workfile_locked(filepath): # add lockfile dialog - from Qt import QtWidgets - top_level_widgets = {w.objectName(): w for w in - QtWidgets.QApplication.topLevelWidgets()} - parent = top_level_widgets.get("MayaWindow", None) - workfile_dialog = WorkfileLockDialog(filepath, parent=parent) - if not workfile_dialog.exec_(): - cmds.file(new=True) - return + try: + workfile_dialog.close() + workfile_dialog.deleteLater() + except: + workfile_dialog = WorkfileLockDialog(filepath) + if not workfile_dialog.exec_(): + cmds.file(new=True) + return create_workfile_lock(filepath) @@ -514,7 +531,6 @@ def on_before_close(): def before_file_open(): """check lock file when the file changed""" - log.info("Removing lock on current file before scene open...") # delete the lock file _remove_workfile_lock() @@ -654,6 +670,13 @@ def before_workfile_save(event): create_workspace_mel(workdir_path, project_name) +def after_workfile_save(event): + workfile_name = event["filename"] + if workfile_name: + if not is_workfile_locked(workfile_name): + create_workfile_lock(workfile_name) + + class MayaDirmap(HostDirmap): def on_enable_dirmap(self): cmds.dirmap(en=True) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 93cc0b153b..7377d10171 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -469,9 +469,7 @@ class FilesWidget(QtWidgets.QWidget): host = self.host if self._is_workfile_locked(filepath): # add lockfile dialog - dialog = WorkfileLockDialog(filepath, parent=self) - if not dialog.exec_(): - return + WorkfileLockDialog(filepath) if isinstance(host, IWorkfileHost): has_unsaved_changes = host.workfile_has_unsaved_changes() From 775b34df06b6af95e87d031576a7bea7a4bd7ef5 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Wed, 14 Sep 2022 20:53:22 +0800 Subject: [PATCH 089/144] adding a Qt lockfile dialog for lockfile tasks --- openpype/hosts/maya/api/pipeline.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index eb22eeeb3b..969680bdf5 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -155,8 +155,11 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): OpenMaya.MSceneMessage.kBeforeSave, _on_scene_save ) - self._op_events[_after_scene_save] = OpenMaya.MSceneMessage.addCallback( - OpenMaya.MSceneMessage.kAfterSave, _after_scene_save + self._op_events[_after_scene_save] = ( + OpenMaya.MSceneMessage.addCallback( + OpenMaya.MSceneMessage.kAfterSave, + _after_scene_save + ) ) self._op_events[_before_scene_save] = ( @@ -243,9 +246,11 @@ def _on_maya_initialized(*args): def _on_scene_new(*args): emit_event("new") + def _after_scene_save(*arg): emit_event("after.save") + def _on_scene_save(*args): emit_event("save") @@ -508,14 +513,10 @@ def check_lock_on_current_file(): if is_workfile_locked(filepath): # add lockfile dialog - try: - workfile_dialog.close() - workfile_dialog.deleteLater() - except: - workfile_dialog = WorkfileLockDialog(filepath) - if not workfile_dialog.exec_(): - cmds.file(new=True) - return + workfile_dialog = WorkfileLockDialog(filepath) + if not workfile_dialog.exec_(): + cmds.file(new=True) + return create_workfile_lock(filepath) From 8e068308c6369f50d220a58a81288f1f57337365 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 15:30:46 +0200 Subject: [PATCH 090/144] Add Display Textures settings correctly, labelize the Show settings to clarify what they are --- .../schemas/schema_maya_capture.json | 77 ++++++++++--------- 1 file changed, 41 insertions(+), 36 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 7a40f349cc..ae6c428faf 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -195,6 +195,11 @@ { "nolights": "No Lights"} ] }, + { + "type": "boolean", + "key": "displayTextures", + "label": "Display Textures" + }, { "type": "number", "key": "textureMaxResolution", @@ -217,11 +222,6 @@ "key": "shadows", "label": "Display Shadows" }, - { - "type": "boolean", - "key": "textures", - "label": "Display Textures" - }, { "type": "boolean", "key": "twoSidedLighting", @@ -372,67 +372,67 @@ { "type": "boolean", "key": "cameras", - "label": "cameras" + "label": "Cameras" }, { "type": "boolean", "key": "clipGhosts", - "label": "clipGhosts" + "label": "Clip Ghosts" }, { "type": "boolean", "key": "controlVertices", - "label": "controlVertices" + "label": "NURBS CVs" }, { "type": "boolean", "key": "deformers", - "label": "deformers" + "label": "Deformers" }, { "type": "boolean", "key": "dimensions", - "label": "dimensions" + "label": "Dimensions" }, { "type": "boolean", "key": "dynamicConstraints", - "label": "dynamicConstraints" + "label": "Dynamic Constraints" }, { "type": "boolean", "key": "dynamics", - "label": "dynamics" + "label": "Dynamics" }, { "type": "boolean", "key": "fluids", - "label": "fluids" + "label": "Fluids" }, { "type": "boolean", "key": "follicles", - "label": "follicles" + "label": "Follicles" }, { "type": "boolean", "key": "gpuCacheDisplayFilter", - "label": "gpuCacheDisplayFilter" + "label": "GPU Cache" }, { "type": "boolean", "key": "greasePencils", - "label": "greasePencils" + "label": "Grease Pencil" }, { "type": "boolean", "key": "grid", - "label": "grid" + "label": "Grid" }, { "type": "boolean", "key": "hairSystems", - "label": "hairSystems" + "label": "Hair Systems" }, { "type": "boolean", @@ -442,47 +442,47 @@ { "type": "boolean", "key": "hud", - "label": "hud" + "label": "HUD" }, { "type": "boolean", "key": "hulls", - "label": "hulls" + "label": "NURBS Hulls" }, { "type": "boolean", "key": "ikHandles", - "label": "ikHandles" + "label": "IK Handles" }, { "type": "boolean", "key": "imagePlane", - "label": "imagePlane" + "label": "Image Planes" }, { "type": "boolean", "key": "joints", - "label": "joints" + "label": "Joints" }, { "type": "boolean", "key": "lights", - "label": "lights" + "label": "Lights" }, { "type": "boolean", "key": "locators", - "label": "locators" + "label": "Locators" }, { "type": "boolean", "key": "manipulators", - "label": "manipulators" + "label": "Manipulators" }, { "type": "boolean", "key": "motionTrails", - "label": "motionTrails" + "label": "Motion Trails" }, { "type": "boolean", @@ -502,47 +502,52 @@ { "type": "boolean", "key": "nurbsCurves", - "label": "nurbsCurves" + "label": "NURBS Curves" }, { "type": "boolean", "key": "nurbsSurfaces", - "label": "nurbsSurfaces" + "label": "NURBS Surfaces" }, { "type": "boolean", "key": "particleInstancers", - "label": "particleInstancers" + "label": "Particle Instancers" }, { "type": "boolean", "key": "pivots", - "label": "pivots" + "label": "Pivots" }, { "type": "boolean", "key": "planes", - "label": "planes" + "label": "Planes" }, { "type": "boolean", "key": "pluginShapes", - "label": "pluginShapes" + "label": "Plugin Shapes" }, { "type": "boolean", "key": "polymeshes", - "label": "polymeshes" + "label": "Polygons" }, { "type": "boolean", "key": "strokes", - "label": "strokes" + "label": "Strokes" }, { "type": "boolean", "key": "subdivSurfaces", - "label": "subdivSurfaces" + "label": "Subdiv Surfaces" + }, + { + "type": "boolean", + "key": "textures", + "label": "Texture Placements" } ] }, From c6bd26485d191406e96288c7a4ea7e99f2364494 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 15:35:12 +0200 Subject: [PATCH 091/144] Sort a bit more by Label again so that NURBS options are together + fix label for handles --- .../schemas/schema_maya_capture.json | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index ae6c428faf..d2627c1e2a 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -379,11 +379,6 @@ "key": "clipGhosts", "label": "Clip Ghosts" }, - { - "type": "boolean", - "key": "controlVertices", - "label": "NURBS CVs" - }, { "type": "boolean", "key": "deformers", @@ -437,18 +432,13 @@ { "type": "boolean", "key": "handles", - "label": "handles" + "label": "Handles" }, { "type": "boolean", "key": "hud", "label": "HUD" }, - { - "type": "boolean", - "key": "hulls", - "label": "NURBS Hulls" - }, { "type": "boolean", "key": "ikHandles", @@ -499,11 +489,21 @@ "key": "nRigids", "label": "nRigids" }, + { + "type": "boolean", + "key": "controlVertices", + "label": "NURBS CVs" + }, { "type": "boolean", "key": "nurbsCurves", "label": "NURBS Curves" }, + { + "type": "boolean", + "key": "hulls", + "label": "NURBS Hulls" + }, { "type": "boolean", "key": "nurbsSurfaces", From fd4648c9bd48bda91e1c259a5903f4f263668b78 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 15:47:43 +0200 Subject: [PATCH 092/144] Add label --- .../schemas/projects_schema/schemas/schema_maya_capture.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index d2627c1e2a..18e69e92c3 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -369,6 +369,10 @@ { "type": "splitter" }, + { + "type": "label", + "label": "Show" + }, { "type": "boolean", "key": "cameras", From 522d1e2df837bda7611b9667c1afd127337d945f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:04:09 +0200 Subject: [PATCH 093/144] Labelize Camera options to match with Camera attributes in Attribute Editor --- .../schemas/schema_maya_capture.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 18e69e92c3..8c2a460871 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -564,47 +564,47 @@ { "type": "boolean", "key": "displayGateMask", - "label": "displayGateMask" + "label": "Display Gate Mask" }, { "type": "boolean", "key": "displayResolution", - "label": "displayResolution" + "label": "Display Resolution" }, { "type": "boolean", "key": "displayFilmGate", - "label": "displayFilmGate" + "label": "Display Film Gate" }, { "type": "boolean", "key": "displayFieldChart", - "label": "displayFieldChart" + "label": "Display Field Chart" }, { "type": "boolean", "key": "displaySafeAction", - "label": "displaySafeAction" + "label": "Display Safe Action" }, { "type": "boolean", "key": "displaySafeTitle", - "label": "displaySafeTitle" + "label": "Display Safe Title" }, { "type": "boolean", "key": "displayFilmPivot", - "label": "displayFilmPivot" + "label": "Display Film Pivot" }, { "type": "boolean", "key": "displayFilmOrigin", - "label": "displayFilmOrigin" + "label": "Display Film Origin" }, { "type": "number", "key": "overscan", - "label": "overscan", + "label": "Overscan", "decimal": 1, "minimum": 0, "maximum": 10 From 9b9bfdadb993c43c2f6d1232ef522b5326df2cce Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:05:10 +0200 Subject: [PATCH 094/144] Uppercase `percent` label like the surrounding labels --- .../schemas/projects_schema/schemas/schema_maya_capture.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 8c2a460871..32987e7423 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -157,7 +157,7 @@ { "type": "number", "key": "percent", - "label": "percent", + "label": "Percent", "decimal": 1, "minimum": 0, "maximum": 200 From f501dac15ff4d3f0e30b4db4af5caf3485a90c7a Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:14:18 +0200 Subject: [PATCH 095/144] Fix default settings for new viewport options settings --- openpype/settings/defaults/project_settings/maya.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 99ba4cdd5c..8706ea995f 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -693,10 +693,10 @@ "Viewport Options": { "override_viewport_options": true, "displayLights": "default", + "displayTextures": true, "textureMaxResolution": 1024, "renderDepthOfField": true, "shadows": true, - "textures": true, "twoSidedLighting": true, "lineAAEnable": true, "multiSample": 8, @@ -719,7 +719,6 @@ "motionBlurShutterOpenFraction": 0.2, "cameras": false, "clipGhosts": false, - "controlVertices": false, "deformers": false, "dimensions": false, "dynamicConstraints": false, @@ -732,7 +731,6 @@ "hairSystems": true, "handles": false, "hud": false, - "hulls": false, "ikHandles": false, "imagePlane": true, "joints": false, @@ -743,7 +741,9 @@ "nCloths": false, "nParticles": false, "nRigids": false, + "controlVertices": false, "nurbsCurves": false, + "hulls": false, "nurbsSurfaces": false, "particleInstancers": false, "pivots": false, @@ -751,7 +751,8 @@ "pluginShapes": false, "polymeshes": true, "strokes": false, - "subdivSurfaces": false + "subdivSurfaces": false, + "textures": false }, "Camera Options": { "displayGateMask": false, From b9c3c95c2642b19305a57064be74fa0cd6ef12ae Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:16:03 +0200 Subject: [PATCH 096/144] Use `id` variable (cosmetics because it results in same key) --- openpype/hosts/maya/api/lib.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib.py b/openpype/hosts/maya/api/lib.py index 58e160cb2f..6a8447d6ad 100644 --- a/openpype/hosts/maya/api/lib.py +++ b/openpype/hosts/maya/api/lib.py @@ -2483,7 +2483,7 @@ def load_capture_preset(data=None): # DISPLAY OPTIONS id = 'Display Options' disp_options = {} - for key in preset['Display Options']: + for key in preset[id]: if key.startswith('background'): disp_options[key] = preset['Display Options'][key] if len(disp_options[key]) == 4: From 95fef2c4b11e055be8d55bd464e075eb2f1d7415 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:50:32 +0200 Subject: [PATCH 097/144] Fix Width label --- .../schemas/projects_schema/schemas/schema_maya_capture.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 570e22aa60..ffa1e61e68 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -141,7 +141,7 @@ { "type": "number", "key": "width", - "label": " Width", + "label": "Width", "decimal": 0, "minimum": 0, "maximum": 99999 From 1e27e9b71ff18af0aa7d957be6db344f7030a2aa Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:50:50 +0200 Subject: [PATCH 098/144] Remove unused settings --- .../schemas/schema_maya_capture.json | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index ffa1e61e68..2e4d4d67ab 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -153,19 +153,6 @@ "decimal": 0, "minimum": 0, "maximum": 99999 - }, - { - "type": "number", - "key": "percent", - "label": "Percent", - "decimal": 1, - "minimum": 0, - "maximum": 200 - }, - { - "type": "text", - "key": "mode", - "label": "Mode" } ] }, From ee4b9056feae4f942624d6e9bf37bb64a875bfba Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:51:59 +0200 Subject: [PATCH 099/144] Fix incorrectly resolved merge conflict --- .../schemas/projects_schema/schemas/schema_maya_capture.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index 2e4d4d67ab..e23dbbbc1d 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -430,11 +430,6 @@ "key": "headsUpDisplay", "label": "HUD" }, - { - "type": "boolean", - "key": "hulls", - "label": "hulls" - }, { "type": "boolean", "key": "ikHandles", From e16f5df4d7a346ee5b1f7b3c79b146fe9ba3e958 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:53:04 +0200 Subject: [PATCH 100/144] Update defaults for the removed settings --- openpype/settings/defaults/project_settings/maya.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 8b0418f5c6..79e80aec2e 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -686,9 +686,7 @@ }, "Resolution": { "width": 1920, - "height": 1080, - "percent": 1.0, - "mode": "Custom" + "height": 1080 }, "Viewport Options": { "override_viewport_options": true, From 730f451020cb438b6a57756e5713212ae6e2261f Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 16:57:11 +0200 Subject: [PATCH 101/144] Revert "Fix Width label" This reverts commit 95fef2c4b11e055be8d55bd464e075eb2f1d7415. --- .../schemas/projects_schema/schemas/schema_maya_capture.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index e23dbbbc1d..c9904150fd 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -141,7 +141,7 @@ { "type": "number", "key": "width", - "label": "Width", + "label": " Width", "decimal": 0, "minimum": 0, "maximum": 99999 From a0333c88aed89707eba5cbea154f4449639dac44 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Wed, 14 Sep 2022 17:23:59 +0200 Subject: [PATCH 102/144] Remove unused PanZoom / pan_zoom settings --- .../settings/defaults/project_settings/maya.json | 3 --- .../projects_schema/schemas/schema_maya_capture.json | 12 ------------ 2 files changed, 15 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 79e80aec2e..8643297f02 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -678,9 +678,6 @@ "isolate_view": true, "off_screen": true }, - "PanZoom": { - "pan_zoom": true - }, "Renderer": { "rendererName": "vp2Renderer" }, diff --git a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json index c9904150fd..62c33f55fc 100644 --- a/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json +++ b/openpype/settings/entities/schemas/projects_schema/schemas/schema_maya_capture.json @@ -94,18 +94,6 @@ } ] }, - - { - "type": "dict", - "key": "PanZoom", - "children": [ - { - "type": "boolean", - "key": "pan_zoom", - "label": " Pan Zoom" - } - ] - }, { "type": "splitter" }, From 3703ec07bcc709fb69cc57b19a66dc449c88f425 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 14 Sep 2022 18:23:17 +0200 Subject: [PATCH 103/144] OP-3940 - introduced new Settings for CollectVersion for Photoshop --- .../defaults/project_settings/photoshop.json | 3 +++ .../schema_project_photoshop.json | 17 +++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index 552c2c9cad..8ea36a3000 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -15,6 +15,9 @@ "CollectInstances": { "flatten_subset_template": "" }, + "CollectVersion": { + "sync_workfile_version": true + }, "ValidateContainers": { "enabled": true, "optional": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index 7aa49c99a4..500c5d027b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -131,6 +131,23 @@ } ] }, + { + "type": "dict", + "collapsible": true, + "key": "CollectVersion", + "label": "Collect Version", + "children": [ + { + "type": "label", + "label": "Synchronize version for image and review instances by workfile version." + }, + { + "type": "boolean", + "key": "sync_workfile_version", + "label": "Synchronize version with workfile" + } + ] + }, { "type": "schema_template", "name": "template_publish_plugin", From d93a18fd89bf749de62413b9280a21dde871c319 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 14 Sep 2022 18:25:11 +0200 Subject: [PATCH 104/144] OP-3940 - added new collector for Photoshop Single point of control if image and review instances should have their version synchronized according to workfile version. --- .../hosts/photoshop/plugins/CollectVersion.py | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 openpype/hosts/photoshop/plugins/CollectVersion.py diff --git a/openpype/hosts/photoshop/plugins/CollectVersion.py b/openpype/hosts/photoshop/plugins/CollectVersion.py new file mode 100644 index 0000000000..46f48b20fb --- /dev/null +++ b/openpype/hosts/photoshop/plugins/CollectVersion.py @@ -0,0 +1,28 @@ +import pyblish.api + + +class CollectVersion(pyblish.api.InstancePlugin): + """Collect version for publishable instances. + + Used to synchronize version from workfile to all publishable instances: + - image (manually created or color coded) + - review + + Dev comment: + Explicit collector created to control this from single place and not from + 3 different. + """ + order = pyblish.api.CollectorOrder + 0.200 + label = 'Collect Version' + + hosts = ["photoshop"] + families = ["image", "review"] + + # controlled by Settings + sync_workfile_version = False + + def process(self, instance): + if self.sync_workfile_version: + workfile_version = instance.context.data["version"] + self.log.debug(f"Applying version {workfile_version}") + instance.data["version"] = workfile_version From d0b8437cfd86113c6ffa3a04a0959bc80f3ddfcc Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 14 Sep 2022 18:32:08 +0200 Subject: [PATCH 105/144] OP-3940 - added optionality to collector There might be a reason when artist would like to skip this synchronization for specific workfile. --- openpype/hosts/photoshop/plugins/CollectVersion.py | 1 + openpype/settings/defaults/project_settings/photoshop.json | 1 + .../schemas/projects_schema/schema_project_photoshop.json | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/openpype/hosts/photoshop/plugins/CollectVersion.py b/openpype/hosts/photoshop/plugins/CollectVersion.py index 46f48b20fb..bc7af580d7 100644 --- a/openpype/hosts/photoshop/plugins/CollectVersion.py +++ b/openpype/hosts/photoshop/plugins/CollectVersion.py @@ -19,6 +19,7 @@ class CollectVersion(pyblish.api.InstancePlugin): families = ["image", "review"] # controlled by Settings + optional = True sync_workfile_version = False def process(self, instance): diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index 8ea36a3000..43a460052a 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -16,6 +16,7 @@ "flatten_subset_template": "" }, "CollectVersion": { + "optional": true, "sync_workfile_version": true }, "ValidateContainers": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index 500c5d027b..e8dad84859 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -137,6 +137,11 @@ "key": "CollectVersion", "label": "Collect Version", "children": [ + { + "type": "boolean", + "key": "optional", + "label": "Optional" + }, { "type": "label", "label": "Synchronize version for image and review instances by workfile version." From 9ab7647d55b89919043efee3e36852607a1348b9 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Sep 2022 10:57:19 +0200 Subject: [PATCH 106/144] OP-3940 - fixed location and name --- .../plugins/{CollectVersion.py => publish/collect_version.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename openpype/hosts/photoshop/plugins/{CollectVersion.py => publish/collect_version.py} (100%) diff --git a/openpype/hosts/photoshop/plugins/CollectVersion.py b/openpype/hosts/photoshop/plugins/publish/collect_version.py similarity index 100% rename from openpype/hosts/photoshop/plugins/CollectVersion.py rename to openpype/hosts/photoshop/plugins/publish/collect_version.py From 7a5d20ffdb05347736475c86b87e6a782fb5d80f Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Thu, 15 Sep 2022 11:00:20 +0200 Subject: [PATCH 107/144] :bug: skip plugin if otioTimeline is missing --- openpype/plugins/publish/extract_otio_file.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/plugins/publish/extract_otio_file.py b/openpype/plugins/publish/extract_otio_file.py index c692205d81..1a6a82117d 100644 --- a/openpype/plugins/publish/extract_otio_file.py +++ b/openpype/plugins/publish/extract_otio_file.py @@ -16,6 +16,8 @@ class ExtractOTIOFile(publish.Extractor): hosts = ["resolve", "hiero", "traypublisher"] def process(self, instance): + if not instance.context.data.get("otioTimeline"): + return # create representation data if "representations" not in instance.data: instance.data["representations"] = [] From d905740208168114b12ec1a4873601d61c9b1798 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Sep 2022 12:18:07 +0200 Subject: [PATCH 108/144] OP-3940 - collector cannot be optional --- openpype/settings/defaults/project_settings/photoshop.json | 1 - .../schemas/projects_schema/schema_project_photoshop.json | 5 ----- 2 files changed, 6 deletions(-) diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index 43a460052a..8ea36a3000 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -16,7 +16,6 @@ "flatten_subset_template": "" }, "CollectVersion": { - "optional": true, "sync_workfile_version": true }, "ValidateContainers": { diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index e8dad84859..500c5d027b 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -137,11 +137,6 @@ "key": "CollectVersion", "label": "Collect Version", "children": [ - { - "type": "boolean", - "key": "optional", - "label": "Optional" - }, { "type": "label", "label": "Synchronize version for image and review instances by workfile version." From 18e03a8d28bc9e9fb0404c5f92475861e90f4f17 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Sep 2022 12:46:31 +0200 Subject: [PATCH 109/144] OP-3940 - collector cannot be optional --- openpype/hosts/photoshop/plugins/publish/collect_version.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_version.py b/openpype/hosts/photoshop/plugins/publish/collect_version.py index bc7af580d7..46f48b20fb 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_version.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_version.py @@ -19,7 +19,6 @@ class CollectVersion(pyblish.api.InstancePlugin): families = ["image", "review"] # controlled by Settings - optional = True sync_workfile_version = False def process(self, instance): From 9fdb71b814011dc9f94fa9243711660d3854bbfa Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Thu, 15 Sep 2022 13:52:29 +0200 Subject: [PATCH 110/144] increasi size of publisher's main window --- openpype/tools/publisher/window.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/publisher/window.py b/openpype/tools/publisher/window.py index 90a36b4f01..2a0e6e940a 100644 --- a/openpype/tools/publisher/window.py +++ b/openpype/tools/publisher/window.py @@ -30,8 +30,8 @@ from .widgets import ( class PublisherWindow(QtWidgets.QDialog): """Main window of publisher.""" - default_width = 1000 - default_height = 600 + default_width = 1200 + default_height = 700 def __init__(self, parent=None, reset_on_show=None): super(PublisherWindow, self).__init__(parent) From dd0bbf2bb515441e730c7fea6e3f762ae1f186b2 Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 15 Sep 2022 15:04:55 +0200 Subject: [PATCH 111/144] adding running version into issue template --- .github/ISSUE_TEMPLATE/bug_report.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6ed6ae428c..d1e98409c5 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -6,6 +6,8 @@ labels: bug assignees: '' --- +**Running version** +openpype-v3.14.1-nightly.2 **Describe the bug** A clear and concise description of what the bug is. From ee154aca7f73df163a2e3fe361d87f2f7264ca6d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Thu, 15 Sep 2022 15:06:27 +0200 Subject: [PATCH 112/144] issue: improving example string --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d1e98409c5..96e768e420 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,7 +7,7 @@ assignees: '' --- **Running version** -openpype-v3.14.1-nightly.2 +[ex. 3.14.1-nightly.2] **Describe the bug** A clear and concise description of what the bug is. From b405299e92fe718993591892c71d59f76a604b5d Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Sep 2022 15:33:57 +0200 Subject: [PATCH 113/144] OP-3940 - added collector for remote publishes In Webpublisher uploaded workfile name is not relieable, use last published + 1 instead. --- .../publish/collect_published_version.py | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 openpype/hosts/photoshop/plugins/publish/collect_published_version.py diff --git a/openpype/hosts/photoshop/plugins/publish/collect_published_version.py b/openpype/hosts/photoshop/plugins/publish/collect_published_version.py new file mode 100644 index 0000000000..2502689e4b --- /dev/null +++ b/openpype/hosts/photoshop/plugins/publish/collect_published_version.py @@ -0,0 +1,55 @@ +"""Collects published version of workfile and increments it. + +For synchronization of published image and workfile version it is required +to store workfile version from workfile file name in context.data["version"]. +In remote publishing this name is unreliable (artist might not follow naming +convention etc.), last published workfile version for particular workfile +subset is used instead. + +This plugin runs only in remote publishing (eg. Webpublisher). + +Requires: + context.data["assetEntity"] + +Provides: + context["version"] - incremented latest published workfile version +""" + +import pyblish.api + +from openpype.client import get_last_version_by_subset_name + + +class CollectPublishedVersion(pyblish.api.ContextPlugin): + """Collects published version of workfile and increments it.""" + + order = pyblish.api.CollectorOrder + 0.190 + label = "Collect published version" + hosts = ["photoshop"] + targets = ["remotepublish"] + + def process(self, context): + workfile_subset_name = None + for instance in context: + if instance.data["family"] == "workfile": + workfile_subset_name = instance.data["subset"] + break + + if not workfile_subset_name: + self.log.warning("No workfile instance found, " + "synchronization of version will not work.") + return + + project_name = context.data["projectName"] + asset_doc = context.data["assetEntity"] + asset_id = asset_doc["_id"] + + version_doc = get_last_version_by_subset_name(project_name, + workfile_subset_name, + asset_id) + version_int = 1 + if version_doc: + version_int += int(version_doc["name"]) + + self.log.debug(f"Setting {version_int} to context.") + context.data["version"] = version_int From abee82f45e48205c2e995dda9f53d8e92370cfed Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Thu, 15 Sep 2022 15:35:09 +0200 Subject: [PATCH 114/144] OP-3940 - fix wrong import for pype_commands --- openpype/pype_commands.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/pype_commands.py b/openpype/pype_commands.py index 85561495fd..f65d969c53 100644 --- a/openpype/pype_commands.py +++ b/openpype/pype_commands.py @@ -187,7 +187,7 @@ class PypeCommands: (to choose validator for example) """ - from openpype.hosts.webpublisher.cli_functions import ( + from openpype.hosts.webpublisher.publish_functions import ( cli_publish_from_app ) From 9403dc743a341e56c44b7afb2f9549d31a3216fa Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Thu, 15 Sep 2022 23:44:02 +0800 Subject: [PATCH 115/144] adding a Qt lockfile dialog for lockfile tasks --- openpype/hosts/maya/api/pipeline.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/maya/api/pipeline.py b/openpype/hosts/maya/api/pipeline.py index 969680bdf5..c13b47ef4a 100644 --- a/openpype/hosts/maya/api/pipeline.py +++ b/openpype/hosts/maya/api/pipeline.py @@ -112,7 +112,7 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): register_event_callback("taskChanged", on_task_changed) register_event_callback("workfile.open.before", before_workfile_open) register_event_callback("workfile.save.before", before_workfile_save) - register_event_callback("workfile.save.after", after_workfile_save) + register_event_callback("workfile.save.before", after_workfile_save) def open_workfile(self, filepath): return open_file(filepath) @@ -203,7 +203,7 @@ class MayaHost(HostBase, IWorkfileHost, ILoadHost): self.log.info("Installed event handler _on_scene_save..") self.log.info("Installed event handler _before_scene_save..") - self.log.info("Insatall event handler _on_after_save..") + self.log.info("Installed event handler _on_after_save..") self.log.info("Installed event handler _on_scene_new..") self.log.info("Installed event handler _on_maya_initialized..") self.log.info("Installed event handler _on_scene_open..") @@ -496,9 +496,7 @@ def on_before_save(): def on_after_save(): """Check if there is a lockfile after save""" - filepath = current_file() - if not is_workfile_locked(filepath): - create_workfile_lock(filepath) + check_lock_on_current_file() def check_lock_on_current_file(): @@ -621,6 +619,7 @@ def on_new(): "from openpype.hosts.maya.api import lib;" "lib.add_render_layer_change_observer()") lib.set_context_settings() + _remove_workfile_lock() def on_task_changed(): @@ -660,12 +659,14 @@ def on_task_changed(): def before_workfile_open(): - _remove_workfile_lock() + if handle_workfile_locks(): + _remove_workfile_lock() def before_workfile_save(event): project_name = legacy_io.active_project() - _remove_workfile_lock() + if handle_workfile_locks(): + _remove_workfile_lock() workdir_path = event["workdir_path"] if workdir_path: create_workspace_mel(workdir_path, project_name) @@ -673,9 +674,10 @@ def before_workfile_save(event): def after_workfile_save(event): workfile_name = event["filename"] - if workfile_name: - if not is_workfile_locked(workfile_name): - create_workfile_lock(workfile_name) + if handle_workfile_locks(): + if workfile_name: + if not is_workfile_locked(workfile_name): + create_workfile_lock(workfile_name) class MayaDirmap(HostDirmap): From bb6c8817608c7e6e0c5a5bc40b1652361efb1651 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 16 Sep 2022 11:15:25 +0200 Subject: [PATCH 116/144] OP-3940 - renamed sync_workfile_version to enabled --- .../hosts/photoshop/plugins/publish/collect_version.py | 10 +++------- .../settings/defaults/project_settings/photoshop.json | 2 +- .../projects_schema/schema_project_photoshop.json | 4 ++-- 3 files changed, 6 insertions(+), 10 deletions(-) diff --git a/openpype/hosts/photoshop/plugins/publish/collect_version.py b/openpype/hosts/photoshop/plugins/publish/collect_version.py index 46f48b20fb..aff9f13bfb 100644 --- a/openpype/hosts/photoshop/plugins/publish/collect_version.py +++ b/openpype/hosts/photoshop/plugins/publish/collect_version.py @@ -18,11 +18,7 @@ class CollectVersion(pyblish.api.InstancePlugin): hosts = ["photoshop"] families = ["image", "review"] - # controlled by Settings - sync_workfile_version = False - def process(self, instance): - if self.sync_workfile_version: - workfile_version = instance.context.data["version"] - self.log.debug(f"Applying version {workfile_version}") - instance.data["version"] = workfile_version + workfile_version = instance.context.data["version"] + self.log.debug(f"Applying version {workfile_version}") + instance.data["version"] = workfile_version diff --git a/openpype/settings/defaults/project_settings/photoshop.json b/openpype/settings/defaults/project_settings/photoshop.json index 8ea36a3000..9d74df7cd5 100644 --- a/openpype/settings/defaults/project_settings/photoshop.json +++ b/openpype/settings/defaults/project_settings/photoshop.json @@ -16,7 +16,7 @@ "flatten_subset_template": "" }, "CollectVersion": { - "sync_workfile_version": true + "enabled": false }, "ValidateContainers": { "enabled": true, diff --git a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json index 500c5d027b..e63e25d2c2 100644 --- a/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json +++ b/openpype/settings/entities/schemas/projects_schema/schema_project_photoshop.json @@ -143,8 +143,8 @@ }, { "type": "boolean", - "key": "sync_workfile_version", - "label": "Synchronize version with workfile" + "key": "enabled", + "label": "Enabled" } ] }, From 52e1b563672d757a19b7ab0452f291189f10de6f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 16 Sep 2022 11:47:05 +0200 Subject: [PATCH 117/144] OP-3952 - added text filter on project name to Tray Publisher --- openpype/tools/traypublisher/window.py | 13 +++++++++++++ openpype/tools/utils/models.py | 4 +++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index cc33287091..d161afd37b 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -7,6 +7,7 @@ publishing plugins. """ from Qt import QtWidgets, QtCore +import qtawesome from openpype.pipeline import ( install_host, @@ -43,6 +44,7 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): projects_model = ProjectModel(dbcon) projects_proxy = ProjectSortFilterProxy() projects_proxy.setSourceModel(projects_model) + projects_proxy.setFilterKeyColumn(0) projects_view = QtWidgets.QListView(content_widget) projects_view.setObjectName("ChooseProjectView") @@ -59,10 +61,17 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): btns_layout.addWidget(cancel_btn, 0) btns_layout.addWidget(confirm_btn, 0) + txt_filter = QtWidgets.QLineEdit() + txt_filter.setPlaceholderText("Quick filter projects..") + txt_filter.setClearButtonEnabled(True) + txt_filter.addAction(qtawesome.icon("fa.filter", color="gray"), + QtWidgets.QLineEdit.LeadingPosition) + content_layout = QtWidgets.QVBoxLayout(content_widget) content_layout.setContentsMargins(0, 0, 0, 0) content_layout.setSpacing(20) content_layout.addWidget(header_label, 0) + content_layout.addWidget(txt_filter, 0) content_layout.addWidget(projects_view, 1) content_layout.addLayout(btns_layout, 0) @@ -79,11 +88,15 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): projects_view.doubleClicked.connect(self._on_double_click) confirm_btn.clicked.connect(self._on_confirm_click) cancel_btn.clicked.connect(self._on_cancel_click) + txt_filter.textChanged.connect( + lambda: projects_proxy.setFilterRegularExpression( + txt_filter.text())) self._projects_view = projects_view self._projects_model = projects_model self._cancel_btn = cancel_btn self._confirm_btn = confirm_btn + self._txt_filter = txt_filter self._publisher_window = publisher_window self._project_name = None diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index 1faccef4dd..817d9c0944 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -356,10 +356,12 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): def filterAcceptsRow(self, source_row, source_parent): index = self.sourceModel().index(source_row, 0, source_parent) + string_pattern = self.filterRegularExpression().pattern() if self._filter_enabled: result = self._custom_index_filter(index) if result is not None: - return result + project_name = index.data(PROJECT_NAME_ROLE) + return string_pattern in project_name return super(ProjectSortFilterProxy, self).filterAcceptsRow( source_row, source_parent From ef33cda784b591e1faa899b9c4db9994146222fe Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 16 Sep 2022 12:46:06 +0200 Subject: [PATCH 118/144] OP-3952 - used PlaceholderLineEdit Changed lambda to separate method as lamba is supposed to have some issue during QtDestroy --- openpype/tools/traypublisher/window.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index d161afd37b..6c17c66016 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -20,6 +20,7 @@ from openpype.tools.utils.models import ( ProjectModel, ProjectSortFilterProxy ) +from openpype.tools.utils import PlaceholderLineEdit class StandaloneOverlayWidget(QtWidgets.QFrame): @@ -61,7 +62,7 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): btns_layout.addWidget(cancel_btn, 0) btns_layout.addWidget(confirm_btn, 0) - txt_filter = QtWidgets.QLineEdit() + txt_filter = PlaceholderLineEdit(content_widget) txt_filter.setPlaceholderText("Quick filter projects..") txt_filter.setClearButtonEnabled(True) txt_filter.addAction(qtawesome.icon("fa.filter", color="gray"), @@ -88,12 +89,11 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): projects_view.doubleClicked.connect(self._on_double_click) confirm_btn.clicked.connect(self._on_confirm_click) cancel_btn.clicked.connect(self._on_cancel_click) - txt_filter.textChanged.connect( - lambda: projects_proxy.setFilterRegularExpression( - txt_filter.text())) + txt_filter.textChanged.connect(self._on_text_changed) self._projects_view = projects_view self._projects_model = projects_model + self._projects_proxy = projects_proxy self._cancel_btn = cancel_btn self._confirm_btn = confirm_btn self._txt_filter = txt_filter @@ -115,6 +115,10 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): def _on_cancel_click(self): self._set_project(self._project_name) + def _on_text_changed(self): + self._projects_proxy.setFilterRegularExpression( + self._txt_filter.text()) + def set_selected_project(self): index = self._projects_view.currentIndex() From eafae24e239f4130e3f6c4069b0d085218757614 Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 16 Sep 2022 13:53:28 +0200 Subject: [PATCH 119/144] copy of workfile does not use 'copy' function but 'copyfile' --- openpype/tools/workfiles/files_widget.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/tools/workfiles/files_widget.py b/openpype/tools/workfiles/files_widget.py index 7377d10171..b7d31e4af4 100644 --- a/openpype/tools/workfiles/files_widget.py +++ b/openpype/tools/workfiles/files_widget.py @@ -578,7 +578,7 @@ class FilesWidget(QtWidgets.QWidget): src = self._get_selected_filepath() dst = os.path.join(self._workfiles_root, work_file) - shutil.copy(src, dst) + shutil.copyfile(src, dst) self.workfile_created.emit(dst) @@ -675,7 +675,7 @@ class FilesWidget(QtWidgets.QWidget): else: self.host.save_file(filepath) else: - shutil.copy(src_path, filepath) + shutil.copyfile(src_path, filepath) if isinstance(self.host, IWorkfileHost): self.host.open_workfile(filepath) else: From 210a3e05525913f7439b805c9671e38b7bae40db Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 16 Sep 2022 14:19:07 +0200 Subject: [PATCH 120/144] OP-3953 - added persisting of last selected project in Tray Publisher Last selected project is stored in .json in app folder - eg. next to unzipped version zip. --- openpype/tools/traypublisher/window.py | 32 ++++++++++++++++++++++++++ openpype/tools/utils/models.py | 13 +++++++++++ 2 files changed, 45 insertions(+) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index cc33287091..2d32b5d6bf 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -19,6 +19,25 @@ from openpype.tools.utils.models import ( ProjectModel, ProjectSortFilterProxy ) +import appdirs +from openpype.lib import JSONSettingRegistry + + +class TrayPublisherRegistry(JSONSettingRegistry): + """Class handling OpenPype general settings registry. + + Attributes: + vendor (str): Name used for path construction. + product (str): Additional name used for path construction. + + """ + + def __init__(self): + self.vendor = "pypeclub" + self.product = "openpype" + name = "tray_publisher" + path = appdirs.user_data_dir(self.product, self.vendor) + super(TrayPublisherRegistry, self).__init__(name, path) class StandaloneOverlayWidget(QtWidgets.QFrame): @@ -90,6 +109,16 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): def showEvent(self, event): self._projects_model.refresh() + + setting_registry = TrayPublisherRegistry() + project_name = setting_registry.get_item("project_name") + if project_name: + index = self._projects_model.get_index(project_name) + if index: + mode = QtCore.QItemSelectionModel.Select | \ + QtCore.QItemSelectionModel.Rows + self._projects_view.selectionModel().select(index, mode) + self._cancel_btn.setVisible(self._project_name is not None) super(StandaloneOverlayWidget, self).showEvent(event) @@ -119,6 +148,9 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): self.setVisible(False) self.project_selected.emit(project_name) + setting_registry = TrayPublisherRegistry() + setting_registry.set_item("project_name", project_name) + class TrayPublishWindow(PublisherWindow): def __init__(self, *args, **kwargs): diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index 1faccef4dd..2d917fcc49 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -330,6 +330,19 @@ class ProjectModel(QtGui.QStandardItemModel): if new_items: root_item.appendRows(new_items) + def get_index(self, project_name): + """ + Get index of 'project_name' value. + + Args: + project_name (str): + Returns: + (QModelIndex) + """ + val = self._items_by_name.get(project_name) + if val: + return self.indexFromItem(val) + class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): def __init__(self, *args, **kwargs): From f24925dfd28fe0e053fcdb25239341d6056b863e Mon Sep 17 00:00:00 2001 From: Jakub Trllo Date: Fri, 16 Sep 2022 14:21:52 +0200 Subject: [PATCH 121/144] resaved default settings to add missing values --- .../defaults/project_settings/blender.json | 32 +++++++++---------- .../defaults/project_settings/houdini.json | 3 +- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/openpype/settings/defaults/project_settings/blender.json b/openpype/settings/defaults/project_settings/blender.json index 2720e0286d..7acecfaae0 100644 --- a/openpype/settings/defaults/project_settings/blender.json +++ b/openpype/settings/defaults/project_settings/blender.json @@ -36,35 +36,35 @@ "layout" ] }, - "ExtractBlendAnimation": { - "enabled": true, - "optional": true, - "active": true - }, - "ExtractCamera": { - "enabled": true, - "optional": true, - "active": true - }, "ExtractFBX": { "enabled": true, "optional": true, "active": false }, - "ExtractAnimationFBX": { - "enabled": true, - "optional": true, - "active": false - }, "ExtractABC": { "enabled": true, "optional": true, "active": false }, + "ExtractBlendAnimation": { + "enabled": true, + "optional": true, + "active": true + }, + "ExtractAnimationFBX": { + "enabled": true, + "optional": true, + "active": false + }, + "ExtractCamera": { + "enabled": true, + "optional": true, + "active": true + }, "ExtractLayout": { "enabled": true, "optional": true, "active": false } } -} +} \ No newline at end of file diff --git a/openpype/settings/defaults/project_settings/houdini.json b/openpype/settings/defaults/project_settings/houdini.json index af0789ff8a..cdf829db57 100644 --- a/openpype/settings/defaults/project_settings/houdini.json +++ b/openpype/settings/defaults/project_settings/houdini.json @@ -6,7 +6,8 @@ "windows": "", "darwin": "", "linux": "" - } + }, + "shelf_definition": [] } ], "create": { From 7331fd20691ba2a7047d98e7baa0e7c04ce750e6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 16 Sep 2022 14:28:04 +0200 Subject: [PATCH 122/144] formatting change Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/tools/traypublisher/window.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index 2d32b5d6bf..56c5594638 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -115,8 +115,9 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): if project_name: index = self._projects_model.get_index(project_name) if index: - mode = QtCore.QItemSelectionModel.Select | \ - QtCore.QItemSelectionModel.Rows + mode = ( + QtCore.QItemSelectionModel.Select + | QtCore.QItemSelectionModel.Rows) self._projects_view.selectionModel().select(index, mode) self._cancel_btn.setVisible(self._project_name is not None) From 563515c0e63f41dbb2d2c01a7518017bf88e7602 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 16 Sep 2022 20:56:12 +0800 Subject: [PATCH 123/144] remove lockfile during publish --- openpype/hosts/maya/plugins/publish/save_scene.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/save_scene.py b/openpype/hosts/maya/plugins/publish/save_scene.py index 50a2f2112a..5a317f4b53 100644 --- a/openpype/hosts/maya/plugins/publish/save_scene.py +++ b/openpype/hosts/maya/plugins/publish/save_scene.py @@ -1,5 +1,9 @@ import pyblish.api - +from openpype.pipeline.workfile.lock_workfile import( + is_workfile_lock_enabled, + remove_workfile_lock +) +from openpype.pipeline import legacy_io class SaveCurrentScene(pyblish.api.ContextPlugin): """Save current scene @@ -23,5 +27,8 @@ class SaveCurrentScene(pyblish.api.ContextPlugin): "are no modifications..") return + active_project = legacy_io.active_project() + if is_workfile_lock_enabled("maya", active_project): + remove_workfile_lock(current) self.log.info("Saving current file..") cmds.file(save=True, force=True) From c2d9ef859e7fbaa697e32ef869c69365e06b9e85 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 16 Sep 2022 21:00:03 +0800 Subject: [PATCH 124/144] remove lockfile during publish --- openpype/hosts/maya/plugins/publish/save_scene.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/maya/plugins/publish/save_scene.py b/openpype/hosts/maya/plugins/publish/save_scene.py index 5a317f4b53..99d486e545 100644 --- a/openpype/hosts/maya/plugins/publish/save_scene.py +++ b/openpype/hosts/maya/plugins/publish/save_scene.py @@ -1,4 +1,5 @@ import pyblish.api + from openpype.pipeline.workfile.lock_workfile import( is_workfile_lock_enabled, remove_workfile_lock @@ -28,6 +29,7 @@ class SaveCurrentScene(pyblish.api.ContextPlugin): return active_project = legacy_io.active_project() + # remove lockfile before saving if is_workfile_lock_enabled("maya", active_project): remove_workfile_lock(current) self.log.info("Saving current file..") From 1049f22c4d5f43de8a5c81363ecf77ed10728baf Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 16 Sep 2022 21:01:45 +0800 Subject: [PATCH 125/144] remove lockfile during publish --- openpype/hosts/maya/plugins/publish/save_scene.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/save_scene.py b/openpype/hosts/maya/plugins/publish/save_scene.py index 99d486e545..33a297889a 100644 --- a/openpype/hosts/maya/plugins/publish/save_scene.py +++ b/openpype/hosts/maya/plugins/publish/save_scene.py @@ -1,6 +1,5 @@ import pyblish.api - -from openpype.pipeline.workfile.lock_workfile import( +from openpype.pipeline.workfile.lock_workfile import ( is_workfile_lock_enabled, remove_workfile_lock ) From 8fab407da2e203d8815d8c2005cd84285f316737 Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 16 Sep 2022 21:12:22 +0800 Subject: [PATCH 126/144] remove lockfile during publish --- openpype/hosts/maya/plugins/publish/save_scene.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/save_scene.py b/openpype/hosts/maya/plugins/publish/save_scene.py index 33a297889a..15472dba96 100644 --- a/openpype/hosts/maya/plugins/publish/save_scene.py +++ b/openpype/hosts/maya/plugins/publish/save_scene.py @@ -26,10 +26,10 @@ class SaveCurrentScene(pyblish.api.ContextPlugin): self.log.debug("Skipping file save as there " "are no modifications..") return - - active_project = legacy_io.active_project() + project_name = context.data["projectName"] + project_settings = context.data["project_settings"] # remove lockfile before saving - if is_workfile_lock_enabled("maya", active_project): + if is_workfile_lock_enabled("maya", project_name, project_settings): remove_workfile_lock(current) self.log.info("Saving current file..") cmds.file(save=True, force=True) From 42ad74b398c21aee19f5fa4d7f7710aaf4c4673d Mon Sep 17 00:00:00 2001 From: Kayla Man Date: Fri, 16 Sep 2022 21:13:14 +0800 Subject: [PATCH 127/144] remove lockfile during publish --- openpype/hosts/maya/plugins/publish/save_scene.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/maya/plugins/publish/save_scene.py b/openpype/hosts/maya/plugins/publish/save_scene.py index 15472dba96..45e62e7b44 100644 --- a/openpype/hosts/maya/plugins/publish/save_scene.py +++ b/openpype/hosts/maya/plugins/publish/save_scene.py @@ -3,7 +3,7 @@ from openpype.pipeline.workfile.lock_workfile import ( is_workfile_lock_enabled, remove_workfile_lock ) -from openpype.pipeline import legacy_io + class SaveCurrentScene(pyblish.api.ContextPlugin): """Save current scene From d1cc57b2e5aff77147ad8d9ba54e7bb3d4fe4b64 Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 17 Sep 2022 04:11:42 +0000 Subject: [PATCH 128/144] [Automated] Bump version --- CHANGELOG.md | 45 +++++++++++++++++++++++---------------------- openpype/version.py | 2 +- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d6b620d58..af347cadfe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,18 +1,39 @@ # Changelog -## [3.14.3-nightly.1](https://github.com/pypeclub/OpenPype/tree/HEAD) +## [3.14.3-nightly.2](https://github.com/pypeclub/OpenPype/tree/HEAD) [Full Changelog](https://github.com/pypeclub/OpenPype/compare/3.14.2...HEAD) **🚀 Enhancements** +- Github issues adding `running version` section [\#3864](https://github.com/pypeclub/OpenPype/pull/3864) +- Publisher: Increase size of main window [\#3862](https://github.com/pypeclub/OpenPype/pull/3862) +- Houdini: Increment current file on workfile publish [\#3840](https://github.com/pypeclub/OpenPype/pull/3840) - Publisher: Add new publisher to host tools [\#3833](https://github.com/pypeclub/OpenPype/pull/3833) +- General: lock task workfiles when they are working on [\#3810](https://github.com/pypeclub/OpenPype/pull/3810) - Maya: Workspace mel loaded from settings [\#3790](https://github.com/pypeclub/OpenPype/pull/3790) **🐛 Bug fixes** +- Settings: Add missing default settings [\#3870](https://github.com/pypeclub/OpenPype/pull/3870) +- General: Copy of workfile does not use 'copy' function but 'copyfile' [\#3869](https://github.com/pypeclub/OpenPype/pull/3869) +- Tray Publisher: skip plugin if otioTimeline is missing [\#3856](https://github.com/pypeclub/OpenPype/pull/3856) +- Maya: Extract Playblast fix textures + labelize viewport show settings [\#3852](https://github.com/pypeclub/OpenPype/pull/3852) - Ftrack: Url validation does not require ftrackapp [\#3834](https://github.com/pypeclub/OpenPype/pull/3834) - Maya+Ftrack: Change typo in family name `mayaascii` -\> `mayaAscii` [\#3820](https://github.com/pypeclub/OpenPype/pull/3820) +- Maya Deadline: Fix Tile Rendering by forcing integer pixel values [\#3758](https://github.com/pypeclub/OpenPype/pull/3758) + +**🔀 Refactored code** + +- Hiero: Use new Extractor location [\#3851](https://github.com/pypeclub/OpenPype/pull/3851) +- Maya: Remove old legacy \(ftrack\) plug-ins that are of no use anymore [\#3819](https://github.com/pypeclub/OpenPype/pull/3819) +- Nuke: Use new Extractor location [\#3799](https://github.com/pypeclub/OpenPype/pull/3799) +- Maya: Use new Extractor location [\#3775](https://github.com/pypeclub/OpenPype/pull/3775) +- General: Change publish template settings location [\#3755](https://github.com/pypeclub/OpenPype/pull/3755) + +**Merged pull requests:** + +- Remove lockfile during publish [\#3874](https://github.com/pypeclub/OpenPype/pull/3874) ## [3.14.2](https://github.com/pypeclub/OpenPype/tree/3.14.2) (2022-09-12) @@ -21,7 +42,6 @@ **🆕 New features** - Nuke: Build workfile by template [\#3763](https://github.com/pypeclub/OpenPype/pull/3763) -- Houdini: Publishing workfiles [\#3697](https://github.com/pypeclub/OpenPype/pull/3697) **🚀 Enhancements** @@ -57,12 +77,10 @@ - General: Move create project folders to pipeline [\#3768](https://github.com/pypeclub/OpenPype/pull/3768) - General: Create project function moved to client code [\#3766](https://github.com/pypeclub/OpenPype/pull/3766) - Maya: Refactor submit deadline to use AbstractSubmitDeadline [\#3759](https://github.com/pypeclub/OpenPype/pull/3759) -- General: Change publish template settings location [\#3755](https://github.com/pypeclub/OpenPype/pull/3755) - General: Move hostdirname functionality into host [\#3749](https://github.com/pypeclub/OpenPype/pull/3749) - General: Move publish utils to pipeline [\#3745](https://github.com/pypeclub/OpenPype/pull/3745) - Houdini: Define houdini as addon [\#3735](https://github.com/pypeclub/OpenPype/pull/3735) - Fusion: Defined fusion as addon [\#3733](https://github.com/pypeclub/OpenPype/pull/3733) -- Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) - Resolve: Define resolve as addon [\#3727](https://github.com/pypeclub/OpenPype/pull/3727) **Merged pull requests:** @@ -74,17 +92,10 @@ [Full Changelog](https://github.com/pypeclub/OpenPype/compare/CI/3.14.1-nightly.4...3.14.1) -### 📖 Documentation - -- Documentation: Few updates [\#3698](https://github.com/pypeclub/OpenPype/pull/3698) - **🚀 Enhancements** - General: Thumbnail can use project roots [\#3750](https://github.com/pypeclub/OpenPype/pull/3750) - Settings: Remove settings lock on tray exit [\#3720](https://github.com/pypeclub/OpenPype/pull/3720) -- General: Added helper getters to modules manager [\#3712](https://github.com/pypeclub/OpenPype/pull/3712) -- Unreal: Define unreal as module and use host class [\#3701](https://github.com/pypeclub/OpenPype/pull/3701) -- Settings: Lock settings UI session [\#3700](https://github.com/pypeclub/OpenPype/pull/3700) **🐛 Bug fixes** @@ -94,10 +105,6 @@ - Nuke: missing job dependency if multiple bake streams [\#3737](https://github.com/pypeclub/OpenPype/pull/3737) - Nuke: color-space settings from anatomy is working [\#3721](https://github.com/pypeclub/OpenPype/pull/3721) - Settings: Fix studio default anatomy save [\#3716](https://github.com/pypeclub/OpenPype/pull/3716) -- Maya: Use project name instead of project code [\#3709](https://github.com/pypeclub/OpenPype/pull/3709) -- Settings: Fix project overrides save [\#3708](https://github.com/pypeclub/OpenPype/pull/3708) -- Workfiles tool: Fix published workfile filtering [\#3704](https://github.com/pypeclub/OpenPype/pull/3704) -- PS, AE: Provide default variant value for workfile subset [\#3703](https://github.com/pypeclub/OpenPype/pull/3703) **🔀 Refactored code** @@ -106,6 +113,7 @@ - Webpublisher: Webpublisher is used as addon [\#3740](https://github.com/pypeclub/OpenPype/pull/3740) - Photoshop: Defined photoshop as addon [\#3736](https://github.com/pypeclub/OpenPype/pull/3736) - Harmony: Defined harmony as addon [\#3734](https://github.com/pypeclub/OpenPype/pull/3734) +- Flame: Defined flame as addon [\#3732](https://github.com/pypeclub/OpenPype/pull/3732) - General: Module interfaces cleanup [\#3731](https://github.com/pypeclub/OpenPype/pull/3731) - AfterEffects: Move AE functions from general lib [\#3730](https://github.com/pypeclub/OpenPype/pull/3730) - Blender: Define blender as module [\#3729](https://github.com/pypeclub/OpenPype/pull/3729) @@ -114,17 +122,10 @@ - Nuke: Define nuke as module [\#3724](https://github.com/pypeclub/OpenPype/pull/3724) - General: Move subset name functionality [\#3723](https://github.com/pypeclub/OpenPype/pull/3723) - General: Move creators plugin getter [\#3714](https://github.com/pypeclub/OpenPype/pull/3714) -- General: Move constants from lib to client [\#3713](https://github.com/pypeclub/OpenPype/pull/3713) -- Loader: Subset groups using client operations [\#3710](https://github.com/pypeclub/OpenPype/pull/3710) -- TVPaint: Defined as module [\#3707](https://github.com/pypeclub/OpenPype/pull/3707) -- StandalonePublisher: Define StandalonePublisher as module [\#3706](https://github.com/pypeclub/OpenPype/pull/3706) -- TrayPublisher: Define TrayPublisher as module [\#3705](https://github.com/pypeclub/OpenPype/pull/3705) -- General: Move context specific functions to context tools [\#3702](https://github.com/pypeclub/OpenPype/pull/3702) **Merged pull requests:** - Hiero: Define hiero as module [\#3717](https://github.com/pypeclub/OpenPype/pull/3717) -- Deadline: better logging for DL webservice failures [\#3694](https://github.com/pypeclub/OpenPype/pull/3694) ## [3.14.0](https://github.com/pypeclub/OpenPype/tree/3.14.0) (2022-08-18) diff --git a/openpype/version.py b/openpype/version.py index e8a65b04d2..a2335b696b 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.3-nightly.1" +__version__ = "3.14.3-nightly.2" From ad4211656b7651b7eef42351aa13240add36a109 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 17 Sep 2022 10:52:36 +0200 Subject: [PATCH 129/144] Remove double setting of additional attributes for Arnold --- openpype/hosts/maya/api/lib_rendersettings.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 7cd2193086..67b66b8024 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -133,20 +133,7 @@ class RenderSettings(object): cmds.setAttr( "defaultArnoldDriver.mergeAOVs", multi_exr) - # Passes additional options in from the schema as a list - # but converts it to a dictionary because ftrack doesn't - # allow fullstops in custom attributes. Then checks for - # type of MtoA attribute passed to adjust the `setAttr` - # command accordingly. self._additional_attribs_setter(additional_options) - for item in additional_options: - attribute, value = item - if (cmds.getAttr(str(attribute), type=True)) == "long": - cmds.setAttr(str(attribute), int(value)) - elif (cmds.getAttr(str(attribute), type=True)) == "bool": - cmds.setAttr(str(attribute), int(value), type = "Boolean") # noqa - elif (cmds.getAttr(str(attribute), type=True)) == "string": - cmds.setAttr(str(attribute), str(value), type = "string") # noqa reset_frame_range() def _set_redshift_settings(self, width, height): From 2c8eaec2d7c93e53accfec87d828bf8648d38428 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 17 Sep 2022 11:04:12 +0200 Subject: [PATCH 130/144] Remove debug print statement --- openpype/hosts/maya/api/lib_rendersettings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 67b66b8024..a62145e921 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -217,7 +217,6 @@ class RenderSettings(object): cmds.setAttr("defaultRenderGlobals.extensionPadding", 4) def _additional_attribs_setter(self, additional_attribs): - print(additional_attribs) for item in additional_attribs: attribute, value = item if (cmds.getAttr(str(attribute), type=True)) == "long": From 0c71ec1d3840db418a4077efa1eddc57436f4d27 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 17 Sep 2022 13:18:04 +0200 Subject: [PATCH 131/144] Tweak readability, log error on unsupported attribute type --- openpype/hosts/maya/api/lib_rendersettings.py | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index a62145e921..0618420b2b 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -5,6 +5,7 @@ import maya.mel as mel import six import sys +from openpype.lib import Logger from openpype.api import ( get_project_settings, get_current_project_settings @@ -38,6 +39,8 @@ class RenderSettings(object): "underscore": "_" } + log = Logger.get_logger("RenderSettings") + @classmethod def get_image_prefix_attr(cls, renderer): return cls._image_prefix_nodes[renderer] @@ -219,9 +222,16 @@ class RenderSettings(object): def _additional_attribs_setter(self, additional_attribs): for item in additional_attribs: attribute, value = item - if (cmds.getAttr(str(attribute), type=True)) == "long": - cmds.setAttr(str(attribute), int(value)) - elif (cmds.getAttr(str(attribute), type=True)) == "bool": - cmds.setAttr(str(attribute), int(value)) # noqa - elif (cmds.getAttr(str(attribute), type=True)) == "string": - cmds.setAttr(str(attribute), str(value), type = "string") # noqa + attribute = str(attribute) # ensure str conversion from settings + attribute_type = cmds.getAttr(attribute, type=True) + if attribute_type in {"long", "bool"}: + cmds.setAttr(attribute, int(value)) + elif attribute_type == "string": + cmds.setAttr(attribute, str(value), type="string") + else: + self.log.error( + "Attribute {attribute} can not be set due to unsupported " + "type: {attribute_type}".format( + attribute=attribute, + attribute_type=attribute_type) + ) From 83922a87cd08630b549cb6f0220aaacfcb5623ec Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 17 Sep 2022 13:19:37 +0200 Subject: [PATCH 132/144] Add double attribute support (float) --- openpype/hosts/maya/api/lib_rendersettings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/hosts/maya/api/lib_rendersettings.py b/openpype/hosts/maya/api/lib_rendersettings.py index 0618420b2b..777a6ffbc9 100644 --- a/openpype/hosts/maya/api/lib_rendersettings.py +++ b/openpype/hosts/maya/api/lib_rendersettings.py @@ -228,6 +228,8 @@ class RenderSettings(object): cmds.setAttr(attribute, int(value)) elif attribute_type == "string": cmds.setAttr(attribute, str(value), type="string") + elif attribute_type in {"double", "doubleAngle", "doubleLinear"}: + cmds.setAttr(attribute, float(value)) else: self.log.error( "Attribute {attribute} can not be set due to unsupported " From b36e1ded7d24c74b0601d0c65567f4674d6bf212 Mon Sep 17 00:00:00 2001 From: Roy Nieterau Date: Sat, 17 Sep 2022 13:29:38 +0200 Subject: [PATCH 133/144] Set default image format for V-Ray+Redshift to exr instead of png and iff --- openpype/settings/defaults/project_settings/maya.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/settings/defaults/project_settings/maya.json b/openpype/settings/defaults/project_settings/maya.json index 8643297f02..76ef0a7338 100644 --- a/openpype/settings/defaults/project_settings/maya.json +++ b/openpype/settings/defaults/project_settings/maya.json @@ -49,7 +49,7 @@ "vray_renderer": { "image_prefix": "maya///", "engine": "1", - "image_format": "png", + "image_format": "exr", "aov_list": [], "additional_options": [] }, @@ -57,7 +57,7 @@ "image_prefix": "maya///", "primary_gi_engine": "0", "secondary_gi_engine": "0", - "image_format": "iff", + "image_format": "exr", "multilayer_exr": true, "force_combine": true, "aov_list": [], From b48ad01e4c6ada98a96f048d93615fa072190b86 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 10:48:44 +0200 Subject: [PATCH 134/144] Changed function name Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/tools/utils/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index 2d917fcc49..d072ff297d 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -330,7 +330,7 @@ class ProjectModel(QtGui.QStandardItemModel): if new_items: root_item.appendRows(new_items) - def get_index(self, project_name): + def find_project(self, project_name): """ Get index of 'project_name' value. From ae7f7ebabbd69352fab23f7bc9757c90c80881b5 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 10:54:23 +0200 Subject: [PATCH 135/144] OP-3953 - added missing mapping to proxy model --- openpype/tools/traypublisher/window.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index 56c5594638..0c99a55998 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -113,7 +113,10 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): setting_registry = TrayPublisherRegistry() project_name = setting_registry.get_item("project_name") if project_name: - index = self._projects_model.get_index(project_name) + index = None + src_index = self._projects_model.find_project(project_name) + if src_index is not None: + index = self._projects_proxy.mapFromSource(src_index) if index: mode = ( QtCore.QItemSelectionModel.Select From 94aa11f1167e90719ef9426bfae78492b7491366 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 10:57:47 +0200 Subject: [PATCH 136/144] OP-3953 - added missing proxy model variable --- openpype/tools/traypublisher/window.py | 1 + 1 file changed, 1 insertion(+) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index 0c99a55998..ca8c0758d6 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -101,6 +101,7 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): self._projects_view = projects_view self._projects_model = projects_model + self._projects_proxy = projects_proxy self._cancel_btn = cancel_btn self._confirm_btn = confirm_btn From f711b529b9693d73d08d696acba077bf45ba3f77 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 11:01:52 +0200 Subject: [PATCH 137/144] OP-3952 - safer resolving of search pattern --- openpype/tools/utils/models.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index 817d9c0944..6663ca1b0a 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -361,7 +361,9 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): result = self._custom_index_filter(index) if result is not None: project_name = index.data(PROJECT_NAME_ROLE) - return string_pattern in project_name + if project_name is None: + return result + return string_pattern.lower() in project_name.lower() return super(ProjectSortFilterProxy, self).filterAcceptsRow( source_row, source_parent From 31beb5b6b15a4c7b110c21ebafc71eb3163b004b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 11:03:15 +0200 Subject: [PATCH 138/144] OP-3952 - sort projects after refresh --- openpype/tools/traypublisher/window.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index 6c17c66016..b134e8ab86 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -103,6 +103,8 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): def showEvent(self, event): self._projects_model.refresh() + # Sort projects after refresh + self._projects_proxy.sort(0) self._cancel_btn.setVisible(self._project_name is not None) super(StandaloneOverlayWidget, self).showEvent(event) From a5b2d8c6bd8a6c6ee6ed636deb3d823d9103aabb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 11:04:47 +0200 Subject: [PATCH 139/144] OP-3952 - set sorting as case insensitive --- openpype/tools/utils/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index 6663ca1b0a..f31a56b2f4 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -335,6 +335,9 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): def __init__(self, *args, **kwargs): super(ProjectSortFilterProxy, self).__init__(*args, **kwargs) self._filter_enabled = True + # Disable case sensitivity + self.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) + self._filter_enabled = True def lessThan(self, left_index, right_index): if left_index.data(PROJECT_NAME_ROLE) is None: From 222c64b0c68f6dbde3ac8b6bdf33db51732449e4 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 11:45:58 +0200 Subject: [PATCH 140/144] OP-3953 - handle missing registry json file --- openpype/tools/traypublisher/window.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/openpype/tools/traypublisher/window.py b/openpype/tools/traypublisher/window.py index ca8c0758d6..f3ed01b151 100644 --- a/openpype/tools/traypublisher/window.py +++ b/openpype/tools/traypublisher/window.py @@ -112,7 +112,11 @@ class StandaloneOverlayWidget(QtWidgets.QFrame): self._projects_model.refresh() setting_registry = TrayPublisherRegistry() - project_name = setting_registry.get_item("project_name") + try: + project_name = setting_registry.get_item("project_name") + except ValueError: + project_name = None + if project_name: index = None src_index = self._projects_model.find_project(project_name) From 091498e13017cc96d0638f34fb03b0f0caee59e8 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 11:57:29 +0200 Subject: [PATCH 141/144] OP-3952 - remove duplicated field Co-authored-by: Jakub Trllo <43494761+iLLiCiTiT@users.noreply.github.com> --- openpype/tools/utils/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openpype/tools/utils/models.py b/openpype/tools/utils/models.py index f31a56b2f4..cff5238a69 100644 --- a/openpype/tools/utils/models.py +++ b/openpype/tools/utils/models.py @@ -337,7 +337,6 @@ class ProjectSortFilterProxy(QtCore.QSortFilterProxyModel): self._filter_enabled = True # Disable case sensitivity self.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive) - self._filter_enabled = True def lessThan(self, left_index, right_index): if left_index.data(PROJECT_NAME_ROLE) is None: From 56dcb25dc056b892af0939236d18abda6967ee96 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Mon, 19 Sep 2022 18:23:08 +0200 Subject: [PATCH 142/144] Updated args to maketx Better logging. --- .../maya/plugins/publish/extract_look.py | 54 ++++++++----------- 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index 91b0da75c6..dd705324b9 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -13,7 +13,7 @@ from maya import cmds # noqa import pyblish.api -from openpype.lib import source_hash +from openpype.lib import source_hash, run_subprocess from openpype.pipeline import legacy_io, publish from openpype.hosts.maya.api import lib @@ -68,7 +68,7 @@ def find_paths_by_hash(texture_hash): return legacy_io.distinct(key, {"type": "version"}) -def maketx(source, destination, *args): +def maketx(source, destination, args, logger): """Make `.tx` using `maketx` with some default settings. The settings are based on default as used in Arnold's @@ -79,7 +79,8 @@ def maketx(source, destination, *args): Args: source (str): Path to source file. destination (str): Writing destination path. - *args: Additional arguments for `maketx`. + args: Additional arguments for `maketx`. + logger Returns: str: Output of `maketx` command. @@ -94,7 +95,7 @@ def maketx(source, destination, *args): "OIIO tool not found in {}".format(maketx_path)) raise AssertionError("OIIO tool not found") - cmd = [ + subprocess_args = [ maketx_path, "-v", # verbose "-u", # update mode @@ -103,27 +104,20 @@ def maketx(source, destination, *args): "--checknan", # use oiio-optimized settings for tile-size, planarconfig, metadata "--oiio", - "--filter lanczos3", - escape_space(source) + "--filter", "lanczos3", + source ] - cmd.extend(args) - cmd.extend(["-o", escape_space(destination)]) + subprocess_args.extend(args) + subprocess_args.extend(["-o", destination]) - cmd = " ".join(cmd) + cmd = " ".join(subprocess_args) + logger.debug(cmd) - CREATE_NO_WINDOW = 0x08000000 # noqa - kwargs = dict(args=cmd, stderr=subprocess.STDOUT) - - if sys.platform == "win32": - kwargs["creationflags"] = CREATE_NO_WINDOW try: - out = subprocess.check_output(**kwargs) - except subprocess.CalledProcessError as exc: - print(exc) - import traceback - - traceback.print_exc() + out = run_subprocess(subprocess_args) + except Exception: + logger.error("Maketx converion failed", exc_info=True) raise return out @@ -524,15 +518,17 @@ class ExtractLook(publish.Extractor): if do_maketx and ext != ".tx": # Produce .tx file in staging if source file is not .tx converted = os.path.join(staging, "resources", fname + ".tx") - + additional_args = [ + "--sattrib", + "sourceHash", + texture_hash + ] if linearize: self.log.info("tx: converting sRGB -> linear") - colorconvert = "--colorconvert sRGB linear" - else: - colorconvert = "" + additional_args.extend(["--colorconvert", "sRGB", "linear"]) config_path = get_ocio_config_path("nuke-default") - color_config = "--colorconfig {0}".format(config_path) + additional_args.extend(["--colorconfig", config_path]) # Ensure folder exists if not os.path.exists(os.path.dirname(converted)): os.makedirs(os.path.dirname(converted)) @@ -541,12 +537,8 @@ class ExtractLook(publish.Extractor): maketx( filepath, converted, - # Include `source-hash` as string metadata - "--sattrib", - "sourceHash", - escape_space(texture_hash), - colorconvert, - color_config + additional_args, + self.log ) return converted, COPY, texture_hash From c43b952357c9c69798adcd3fb04fc2d6dbc85100 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 20 Sep 2022 11:15:39 +0200 Subject: [PATCH 143/144] Updated docstring Co-authored-by: Roy Nieterau --- openpype/hosts/maya/plugins/publish/extract_look.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/hosts/maya/plugins/publish/extract_look.py b/openpype/hosts/maya/plugins/publish/extract_look.py index dd705324b9..403b4ee6bc 100644 --- a/openpype/hosts/maya/plugins/publish/extract_look.py +++ b/openpype/hosts/maya/plugins/publish/extract_look.py @@ -79,8 +79,8 @@ def maketx(source, destination, args, logger): Args: source (str): Path to source file. destination (str): Writing destination path. - args: Additional arguments for `maketx`. - logger + args (list): Additional arguments for `maketx`. + logger (logging.Logger): Logger to log messages to. Returns: str: Output of `maketx` command. From 7e65bdd096b7ee3c9ae927374d2c5c89270cd9b3 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 20 Sep 2022 16:39:00 +0200 Subject: [PATCH 144/144] OP-3923 - fix issue in Hero hardlinks Disk must be NTFS format or it will throw "WinError 1", which matches to EINVAL. Still raise different errors. --- openpype/plugins/publish/integrate_hero_version.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/openpype/plugins/publish/integrate_hero_version.py b/openpype/plugins/publish/integrate_hero_version.py index 96d768e1c1..c0760a5471 100644 --- a/openpype/plugins/publish/integrate_hero_version.py +++ b/openpype/plugins/publish/integrate_hero_version.py @@ -577,8 +577,11 @@ class IntegrateHeroVersion(pyblish.api.InstancePlugin): return except OSError as exc: - # re-raise exception if different than cross drive path - if exc.errno != errno.EXDEV: + # re-raise exception if different than + # EXDEV - cross drive path + # EINVAL - wrong format, must be NTFS + self.log.debug("Hardlink failed with errno:'{}'".format(exc.errno)) + if exc.errno not in [errno.EXDEV, errno.EINVAL]: raise shutil.copy(src_path, dst_path)